diff --git a/migrations/1683047918926_mime-type-counts.ts b/migrations/1683047918926_mime-type-counts.ts new file mode 100644 index 00000000..c241455f --- /dev/null +++ b/migrations/1683047918926_mime-type-counts.ts @@ -0,0 +1,12 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { MigrationBuilder, ColumnDefinitions } from 'node-pg-migrate'; + +export const shorthands: ColumnDefinitions | undefined = undefined; + +export function up(pgm: MigrationBuilder): void { + pgm.createMaterializedView( + 'mime_type_counts', + { data: true }, + `SELECT mime_type, COUNT(*) AS count FROM inscriptions GROUP BY mime_type` + ); +} diff --git a/migrations/1683061444855_sat-rarity-counts.ts b/migrations/1683061444855_sat-rarity-counts.ts new file mode 100644 index 00000000..43f2678f --- /dev/null +++ b/migrations/1683061444855_sat-rarity-counts.ts @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { MigrationBuilder, ColumnDefinitions } from 'node-pg-migrate'; + +export const shorthands: ColumnDefinitions | undefined = undefined; + +export function up(pgm: MigrationBuilder): void { + pgm.createMaterializedView( + 'sat_rarity_counts', + { data: true }, + ` + SELECT sat_rarity, COUNT(*) AS count + FROM inscriptions AS i + INNER JOIN locations AS loc ON loc.inscription_id = i.id + WHERE loc.current = TRUE + GROUP BY sat_rarity + ` + ); +} diff --git a/migrations/1683130423352_inscription-count.ts b/migrations/1683130423352_inscription-count.ts new file mode 100644 index 00000000..3799ab19 --- /dev/null +++ b/migrations/1683130423352_inscription-count.ts @@ -0,0 +1,12 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { MigrationBuilder, ColumnDefinitions } from 'node-pg-migrate'; + +export const shorthands: ColumnDefinitions | undefined = undefined; + +export function up(pgm: MigrationBuilder): void { + pgm.createMaterializedView( + 'inscription_count', + { data: true }, + `SELECT COUNT(*) AS count FROM inscriptions` + ); +} diff --git a/migrations/1683130430977_chain-tip-view.ts b/migrations/1683130430977_chain-tip-view.ts new file mode 100644 index 00000000..41f38c1b --- /dev/null +++ b/migrations/1683130430977_chain-tip-view.ts @@ -0,0 +1,38 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { MigrationBuilder, ColumnDefinitions } from 'node-pg-migrate'; + +export const shorthands: ColumnDefinitions | undefined = undefined; + +export function up(pgm: MigrationBuilder): void { + pgm.dropTable('chain_tip'); + pgm.createMaterializedView( + 'chain_tip', + { data: true }, + `SELECT GREATEST(MAX(block_height), 767430) AS block_height FROM locations` + ); +} + +export function down(pgm: MigrationBuilder): void { + pgm.dropMaterializedView('chain_tip'); + pgm.createTable('chain_tip', { + id: { + type: 'bool', + primaryKey: true, + default: true, + }, + block_height: { + type: 'int', + notNull: true, + default: 767430, // First inscription block height + }, + inscription_count: { + type: 'int', + notNull: true, + default: 0, + }, + }); + // Ensure only a single row can exist + pgm.addConstraint('chain_tip', 'chain_tip_one_row', 'CHECK(id)'); + // Create the single row + pgm.sql('INSERT INTO chain_tip VALUES(DEFAULT)'); +} diff --git a/package-lock.json b/package-lock.json index e4907cb2..d071a8ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "fastify-metrics": "^10.2.0", "node-pg-migrate": "^6.2.2", "pino": "^8.10.0", - "postgres": "^3.3.3", + "postgres": "^3.3.4", "undici": "^5.8.0" }, "devDependencies": { @@ -6910,9 +6910,9 @@ } }, "node_modules/postgres": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.3.3.tgz", - "integrity": "sha512-FGLZOpZSXePRIQu6LJpJUDikzuhplWI80uyyfmKBiljpfO+Z4nuClwpq3/dsRnitxVDsgFN5duJ7eUTaC0meQQ==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.3.4.tgz", + "integrity": "sha512-XVu0+d/Y56pl2lSaf0c7V19AhAEfpVrhID1IENWN8nf0xch6hFq6dAov5dtUX6ZD46wfr1TxvLhxLtV8WnNsOg==", "funding": { "type": "individual", "url": "https://github.com/sponsors/porsager" @@ -17822,9 +17822,9 @@ } }, "postgres": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.3.3.tgz", - "integrity": "sha512-FGLZOpZSXePRIQu6LJpJUDikzuhplWI80uyyfmKBiljpfO+Z4nuClwpq3/dsRnitxVDsgFN5duJ7eUTaC0meQQ==" + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.3.4.tgz", + "integrity": "sha512-XVu0+d/Y56pl2lSaf0c7V19AhAEfpVrhID1IENWN8nf0xch6hFq6dAov5dtUX6ZD46wfr1TxvLhxLtV8WnNsOg==" }, "postgres-array": { "version": "2.0.0", diff --git a/package.json b/package.json index 1fdf0e2e..6d6056f6 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "fastify-metrics": "^10.2.0", "node-pg-migrate": "^6.2.2", "pino": "^8.10.0", - "postgres": "^3.3.3", + "postgres": "^3.3.4", "undici": "^5.8.0" } } diff --git a/src/api/util/helpers.ts b/src/api/util/helpers.ts index b2973d89..a8f526d9 100644 --- a/src/api/util/helpers.ts +++ b/src/api/util/helpers.ts @@ -66,3 +66,7 @@ export function hexToBuffer(hex: string): Buffer { } export const has0xPrefix = (id: string) => id.substr(0, 2).toLowerCase() === '0x'; + +export function normalizedHexString(hex: string): string { + return has0xPrefix(hex) ? hex.substring(2) : hex; +} diff --git a/src/chainhook/helpers.ts b/src/chainhook/helpers.ts index 9528a6a0..9dc4714c 100644 --- a/src/chainhook/helpers.ts +++ b/src/chainhook/helpers.ts @@ -1,4 +1,3 @@ -import { OrdinalSatoshi } from '../api/util/ordinal-satoshi'; import { logger } from '../logger'; import { PgStore } from '../pg/pg-store'; import { ChainhookPayloadCType } from './schemas'; @@ -14,91 +13,5 @@ export async function processInscriptionFeed(payload: unknown, db: PgStore): Pro logger.error(errors, `[inscription_feed] invalid payload`); return; } - for (const event of payload.rollback) { - for (const tx of event.transactions) { - for (const operation of tx.metadata.ordinal_operations) { - if (operation.inscription_revealed) { - const genesis_id = operation.inscription_revealed.inscription_id; - await db.rollBackInscriptionGenesis({ genesis_id }); - logger.info(`[inscription_feed] rollback inscription ${genesis_id}`); - } - if (operation.inscription_transferred) { - const genesis_id = operation.inscription_transferred.inscription_id; - const satpoint = operation.inscription_transferred.satpoint_post_transfer.split(':'); - const output = `${satpoint[0]}:${satpoint[1]}`; - await db.rollBackInscriptionTransfer({ genesis_id, output }); - logger.info(`[inscription_feed] rollback transfer ${genesis_id} ${output}`); - } - } - } - } - for (const event of payload.apply) { - for (const tx of event.transactions) { - for (const operation of tx.metadata.ordinal_operations) { - if (operation.inscription_revealed) { - const reveal = operation.inscription_revealed; - const txId = tx.transaction_identifier.hash.substring(2); - const satoshi = new OrdinalSatoshi(reveal.ordinal_number); - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: reveal.inscription_id, - mime_type: reveal.content_type.split(';')[0], - content_type: reveal.content_type, - content_length: reveal.content_length, - number: reveal.inscription_number, - content: reveal.content_bytes, - fee: reveal.inscription_fee.toString(), - }, - location: { - genesis_id: reveal.inscription_id, - block_height: event.block_identifier.index, - block_hash: event.block_identifier.hash.substring(2), - tx_id: txId, - address: reveal.inscriber_address, - output: `${txId}:0`, - offset: reveal.ordinal_offset.toString(), - value: reveal.inscription_output_value.toString(), - timestamp: event.timestamp, - sat_ordinal: reveal.ordinal_number.toString(), - sat_rarity: satoshi.rarity, - sat_coinbase_height: satoshi.blockHeight, - }, - }); - logger.info( - `[inscription_feed] apply inscription #${reveal.inscription_number} (${reveal.inscription_id}) at block ${event.block_identifier.index}` - ); - } - if (operation.inscription_transferred) { - const transfer = operation.inscription_transferred; - const txId = tx.transaction_identifier.hash.substring(2); - const satpoint = transfer.satpoint_post_transfer.split(':'); - const offset = satpoint[2]; - const output = `${satpoint[0]}:${satpoint[1]}`; - const satoshi = new OrdinalSatoshi(transfer.ordinal_number); - await db.insertInscriptionTransfer({ - location: { - genesis_id: transfer.inscription_id, - block_height: event.block_identifier.index, - block_hash: event.block_identifier.hash, - tx_id: txId, - address: transfer.updated_address, - output: output, - offset: offset ?? null, - value: transfer.post_transfer_output_value - ? transfer.post_transfer_output_value.toString() - : null, - timestamp: event.timestamp, - sat_ordinal: transfer.ordinal_number.toString(), - sat_rarity: satoshi.rarity, - sat_coinbase_height: satoshi.blockHeight, - }, - }); - logger.info( - `[inscription_feed] apply transfer for #${transfer.inscription_number} (${transfer.inscription_id}) to output ${output} at block ${event.block_identifier.index}` - ); - } - } - } - } - await db.updateChainTipInscriptionCount(); + await db.updateInscriptions(payload); } diff --git a/src/chainhook/schemas.ts b/src/chainhook/schemas.ts index b92399d6..0e1bd9c0 100644 --- a/src/chainhook/schemas.ts +++ b/src/chainhook/schemas.ts @@ -8,7 +8,7 @@ const Block = Type.Object({ hash: Type.String(), }); -const InscriptionRevealed = Type.Object({ +const InscriptionRevealedSchema = Type.Object({ content_bytes: Type.String(), content_type: Type.String(), content_length: Type.Integer(), @@ -22,8 +22,9 @@ const InscriptionRevealed = Type.Object({ ordinal_offset: Type.Integer(), satpoint_post_inscription: Type.String(), }); +export type InscriptionRevealed = Static; -const InscriptionTransferred = Type.Object({ +const InscriptionTransferredSchema = Type.Object({ inscription_number: Type.Integer(), inscription_id: Type.String(), ordinal_number: Type.Integer(), @@ -32,10 +33,11 @@ const InscriptionTransferred = Type.Object({ satpoint_post_transfer: Type.String(), post_transfer_output_value: Nullable(Type.Integer()), }); +export type InscriptionTransferred = Static; const OrdinalOperation = Type.Object({ - inscription_revealed: Type.Optional(InscriptionRevealed), - inscription_transferred: Type.Optional(InscriptionTransferred), + inscription_revealed: Type.Optional(InscriptionRevealedSchema), + inscription_transferred: Type.Optional(InscriptionTransferredSchema), }); const Output = Type.Object({ @@ -43,7 +45,7 @@ const Output = Type.Object({ value: Type.Integer(), }); -const Transaction = Type.Object({ +const TransactionSchema = Type.Object({ transaction_identifier: Type.Object({ hash: Type.String() }), operations: Type.Array(Type.Any()), metadata: Type.Object({ @@ -52,14 +54,16 @@ const Transaction = Type.Object({ proof: Nullable(Type.String()), }), }); +export type Transaction = Static; const Event = Type.Object({ block_identifier: Block, parent_block_identifier: Block, timestamp: Type.Integer(), - transactions: Type.Array(Transaction), + transactions: Type.Array(TransactionSchema), metadata: Type.Any(), }); +export type InscriptionEvent = Static; const ChainhookPayload = Type.Object({ apply: Type.Array(Event), diff --git a/src/pg/helpers.ts b/src/pg/helpers.ts index 3fdc1d1d..3461fee2 100644 --- a/src/pg/helpers.ts +++ b/src/pg/helpers.ts @@ -1,7 +1,11 @@ import { Static, Type } from '@sinclair/typebox'; import { TypeCompiler } from '@sinclair/typebox/compiler'; import { hexToBuffer } from '../api/util/helpers'; -import { DbInscriptionInsert } from './types'; +import { + DbInscriptionIndexFilters, + DbInscriptionIndexResultCountType, + DbInscriptionInsert, +} from './types'; const OpJson = Type.Object( { @@ -32,3 +36,26 @@ export function inscriptionContentToJson(inscription: DbInscriptionInsert): OpJs } } } + +/** + * Returns which inscription count is required based on filters sent to the index endpoint. + * @param filters - DbInscriptionIndexFilters + * @returns DbInscriptionIndexResultCountType + */ +export function getIndexResultCountType( + filters?: DbInscriptionIndexFilters +): DbInscriptionIndexResultCountType { + if (!filters) return DbInscriptionIndexResultCountType.all; + // Remove undefined values. + Object.keys(filters).forEach( + key => + filters[key as keyof DbInscriptionIndexFilters] === undefined && + delete filters[key as keyof DbInscriptionIndexFilters] + ); + // Check for selected filter. + if (Object.keys(filters).length === 1) { + if (filters.mime_type) return DbInscriptionIndexResultCountType.mimeType; + if (filters.sat_rarity) return DbInscriptionIndexResultCountType.satRarity; + } + return DbInscriptionIndexResultCountType.custom; +} diff --git a/src/pg/pg-store.ts b/src/pg/pg-store.ts index e8a778d3..699dbf86 100644 --- a/src/pg/pg-store.ts +++ b/src/pg/pg-store.ts @@ -1,14 +1,20 @@ import { Order, OrderBy } from '../api/schemas'; -import { SatoshiRarity } from '../api/util/ordinal-satoshi'; +import { normalizedHexString } from '../api/util/helpers'; +import { OrdinalSatoshi, SatoshiRarity } from '../api/util/ordinal-satoshi'; +import { ChainhookPayload, InscriptionEvent } from '../chainhook/schemas'; import { ENV } from '../env'; import { logger } from '../logger'; -import { inscriptionContentToJson } from './helpers'; +import { getIndexResultCountType, inscriptionContentToJson } from './helpers'; import { runMigrations } from './migrations'; import { connectPostgres } from './postgres-tools'; import { BasePgStore } from './postgres-tools/base-pg-store'; import { DbFullyLocatedInscriptionResult, DbInscriptionContent, + DbInscriptionIndexFilters, + DbInscriptionIndexOrder, + DbInscriptionIndexPaging, + DbInscriptionIndexResultCountType, DbInscriptionInsert, DbJsonContent, DbLocation, @@ -44,16 +50,109 @@ export class PgStore extends BasePgStore { return new PgStore(sql); } - async updateChainTipBlockHeight(args: { blockHeight: number }): Promise { - await this.sql` - UPDATE chain_tip SET block_height = GREATEST(${args.blockHeight}, block_height) - `; - } - - async updateChainTipInscriptionCount(): Promise { - await this.sql` - UPDATE chain_tip SET inscription_count = (SELECT COUNT(*) FROM inscriptions) - `; + /** + * Inserts inscription genesis and transfers from Chainhook events. Also handles rollbacks from + * chain re-orgs and materialized view refreshes. + * @param args - Apply/Rollback Chainhook events + */ + async updateInscriptions(payload: ChainhookPayload): Promise { + const updatedInscriptionIds = new Set(); + await this.sqlWriteTransaction(async sql => { + for (const event of payload.rollback) { + for (const tx of event.transactions) { + for (const operation of tx.metadata.ordinal_operations) { + if (operation.inscription_revealed) { + const genesis_id = operation.inscription_revealed.inscription_id; + await this.rollBackInscriptionGenesis({ genesis_id }); + logger.info(`PgStore rollback inscription ${genesis_id}`); + } + if (operation.inscription_transferred) { + const genesis_id = operation.inscription_transferred.inscription_id; + const satpoint = operation.inscription_transferred.satpoint_post_transfer.split(':'); + const output = `${satpoint[0]}:${satpoint[1]}`; + const id = await this.rollBackInscriptionTransfer({ genesis_id, output }); + if (id) updatedInscriptionIds.add(id); + logger.info(`PgStore rollback transfer ${genesis_id} ${output}`); + } + } + } + } + for (const event of payload.apply) { + const block_hash = normalizedHexString(event.block_identifier.hash); + for (const tx of event.transactions) { + const tx_id = normalizedHexString(tx.transaction_identifier.hash); + for (const operation of tx.metadata.ordinal_operations) { + if (operation.inscription_revealed) { + const reveal = operation.inscription_revealed; + const satoshi = new OrdinalSatoshi(reveal.ordinal_number); + const id = await this.insertInscriptionGenesis({ + inscription: { + genesis_id: reveal.inscription_id, + mime_type: reveal.content_type.split(';')[0], + content_type: reveal.content_type, + content_length: reveal.content_length, + number: reveal.inscription_number, + content: reveal.content_bytes, + fee: reveal.inscription_fee.toString(), + }, + location: { + block_hash, + tx_id, + genesis_id: reveal.inscription_id, + block_height: event.block_identifier.index, + address: reveal.inscriber_address, + output: `${tx_id}:0`, + offset: reveal.ordinal_offset.toString(), + value: reveal.inscription_output_value.toString(), + timestamp: event.timestamp, + sat_ordinal: reveal.ordinal_number.toString(), + sat_rarity: satoshi.rarity, + sat_coinbase_height: satoshi.blockHeight, + }, + }); + if (id) updatedInscriptionIds.add(id); + logger.info( + `PgStore apply inscription #${reveal.inscription_number} (${reveal.inscription_id}) at block ${event.block_identifier.index}` + ); + } + if (operation.inscription_transferred) { + const transfer = operation.inscription_transferred; + const satpoint = transfer.satpoint_post_transfer.split(':'); + const offset = satpoint[2]; + const output = `${satpoint[0]}:${satpoint[1]}`; + const satoshi = new OrdinalSatoshi(transfer.ordinal_number); + const id = await this.insertInscriptionTransfer({ + location: { + block_hash, + tx_id, + genesis_id: transfer.inscription_id, + block_height: event.block_identifier.index, + address: transfer.updated_address, + output: output, + offset: offset ?? null, + value: transfer.post_transfer_output_value + ? transfer.post_transfer_output_value.toString() + : null, + timestamp: event.timestamp, + sat_ordinal: transfer.ordinal_number.toString(), + sat_rarity: satoshi.rarity, + sat_coinbase_height: satoshi.blockHeight, + }, + }); + if (id) updatedInscriptionIds.add(id); + logger.info( + `PgStore apply transfer for #${transfer.inscription_number} (${transfer.inscription_id}) to output ${output} at block ${event.block_identifier.index}` + ); + } + } + } + } + }); + await this.normalizeInscriptionLocations({ inscription_id: Array.from(updatedInscriptionIds) }); + await this.refreshMaterializedView('chain_tip'); + await this.refreshMaterializedView('inscription_count'); + await this.refreshMaterializedView('mime_type_counts'); + await this.refreshMaterializedView('sat_rarity_counts'); } async getChainTipBlockHeight(): Promise { @@ -62,10 +161,30 @@ export class PgStore extends BasePgStore { } async getChainTipInscriptionCount(): Promise { - const result = await this.sql<{ inscription_count: number }[]>` - SELECT inscription_count FROM chain_tip + const result = await this.sql<{ count: number }[]>` + SELECT count FROM inscription_count + `; + return result[0].count; + } + + async getMimeTypeInscriptionCount(mimeType?: string[]): Promise { + if (!mimeType) return 0; + const result = await this.sql<{ count: number }[]>` + SELECT SUM(count) AS count + FROM mime_type_counts + WHERE mime_type IN ${this.sql(mimeType)} + `; + return result[0].count; + } + + async geSatRarityInscriptionCount(satRarity?: SatoshiRarity[]): Promise { + if (!satRarity) return 0; + const result = await this.sql<{ count: number }[]>` + SELECT SUM(count) AS count + FROM sat_rarity_counts + WHERE sat_rarity IN ${this.sql(satRarity)} `; - return result[0].inscription_count; + return result[0].count; } async getMaxInscriptionNumber(): Promise { @@ -80,10 +199,243 @@ export class PgStore extends BasePgStore { return result[0].max.toString(); } - async insertInscriptionGenesis(args: { + async getInscriptionCurrentLocation(args: { output: string }): Promise { + const result = await this.sql` + SELECT ${this.sql(LOCATIONS_COLUMNS)} + FROM locations + WHERE output = ${args.output} + AND current = TRUE + `; + if (result.count === 0) { + return undefined; + } + return result[0]; + } + + async getInscriptionContent( + args: InscriptionIdentifier + ): Promise { + const result = await this.sql` + SELECT content, content_type, content_length + FROM inscriptions + WHERE ${ + 'genesis_id' in args + ? this.sql`genesis_id = ${args.genesis_id}` + : this.sql`number = ${args.number}` + } + `; + if (result.count > 0) { + return result[0]; + } + } + + async getInscriptionETag(args: InscriptionIdentifier): Promise { + const result = await this.sql<{ etag: string }[]>` + SELECT date_part('epoch', l.timestamp)::text AS etag + FROM locations AS l + INNER JOIN inscriptions AS i ON l.inscription_id = i.id + WHERE ${ + 'genesis_id' in args + ? this.sql`i.genesis_id = ${args.genesis_id}` + : this.sql`i.number = ${args.number}` + } + AND l.current = TRUE + `; + if (result.count > 0) { + return result[0].etag; + } + } + + async getInscriptions( + page: DbInscriptionIndexPaging, + filters?: DbInscriptionIndexFilters, + sort?: DbInscriptionIndexOrder + ): Promise> { + return await this.sqlTransaction(async sql => { + // Do we need a filtered `COUNT(*)`? If so, try to use the pre-calculated counts we have in + // materialized views to speed up these queries. + const countType = getIndexResultCountType(filters); + // `ORDER BY` statement + let orderBy = sql`gen.block_height`; + switch (sort?.order_by) { + case OrderBy.ordinal: + orderBy = sql`loc.sat_ordinal`; + break; + case OrderBy.rarity: + orderBy = sql`ARRAY_POSITION(ARRAY['common','uncommon','rare','epic','legendary','mythic'], loc.sat_rarity)`; + break; + } + // `ORDER` statement + const order = sort?.order === Order.asc ? sql`ASC` : sql`DESC`; + const results = await sql<({ total: number } & DbFullyLocatedInscriptionResult)[]>` + SELECT + i.genesis_id, + i.number, + i.mime_type, + i.content_type, + i.content_length, + i.fee AS genesis_fee, + gen.block_height AS genesis_block_height, + gen.block_hash AS genesis_block_hash, + gen.tx_id AS genesis_tx_id, + gen.timestamp AS genesis_timestamp, + gen.address AS genesis_address, + loc.tx_id, + loc.address, + loc.output, + loc.offset, + loc.sat_ordinal, + loc.sat_rarity, + loc.timestamp, + loc.value, + loc.sat_coinbase_height, + ${ + countType === DbInscriptionIndexResultCountType.custom + ? sql`COUNT(*) OVER() as total` + : sql`0 as total` + } + FROM inscriptions AS i + INNER JOIN locations AS loc ON loc.inscription_id = i.id + INNER JOIN locations AS gen ON gen.inscription_id = i.id + WHERE loc.current = TRUE AND gen.genesis = TRUE + ${ + filters?.genesis_id?.length + ? sql`AND i.genesis_id IN ${sql(filters.genesis_id)}` + : sql`` + } + ${ + filters?.genesis_block_height + ? sql`AND gen.block_height = ${filters.genesis_block_height}` + : sql`` + } + ${ + filters?.genesis_block_hash + ? sql`AND gen.block_hash = ${filters.genesis_block_hash}` + : sql`` + } + ${ + filters?.from_genesis_block_height + ? sql`AND gen.block_height >= ${filters.from_genesis_block_height}` + : sql`` + } + ${ + filters?.to_genesis_block_height + ? sql`AND gen.block_height <= ${filters.to_genesis_block_height}` + : sql`` + } + ${ + filters?.from_sat_coinbase_height + ? sql`AND loc.sat_coinbase_height >= ${filters.from_sat_coinbase_height}` + : sql`` + } + ${ + filters?.to_sat_coinbase_height + ? sql`AND loc.sat_coinbase_height <= ${filters.to_sat_coinbase_height}` + : sql`` + } + ${ + filters?.from_genesis_timestamp + ? sql`AND gen.timestamp >= to_timestamp(${filters.from_genesis_timestamp})` + : sql`` + } + ${ + filters?.to_genesis_timestamp + ? sql`AND gen.timestamp <= to_timestamp(${filters.to_genesis_timestamp})` + : sql`` + } + ${ + filters?.from_sat_ordinal + ? sql`AND loc.sat_ordinal >= ${filters.from_sat_ordinal}` + : sql`` + } + ${filters?.to_sat_ordinal ? sql`AND loc.sat_ordinal <= ${filters.to_sat_ordinal}` : sql``} + ${filters?.number?.length ? sql`AND i.number IN ${sql(filters.number)}` : sql``} + ${filters?.from_number ? sql`AND i.number >= ${filters.from_number}` : sql``} + ${filters?.to_number ? sql`AND i.number <= ${filters.to_number}` : sql``} + ${filters?.address?.length ? sql`AND loc.address IN ${sql(filters.address)}` : sql``} + ${filters?.mime_type?.length ? sql`AND i.mime_type IN ${sql(filters.mime_type)}` : sql``} + ${filters?.output ? sql`AND loc.output = ${filters.output}` : sql``} + ${ + filters?.sat_rarity?.length + ? sql`AND loc.sat_rarity IN ${sql(filters.sat_rarity)}` + : sql`` + } + ${filters?.sat_ordinal ? sql`AND loc.sat_ordinal = ${filters.sat_ordinal}` : sql``} + ORDER BY ${orderBy} ${order} + LIMIT ${page.limit} + OFFSET ${page.offset} + `; + let total = results[0]?.total ?? 0; + switch (countType) { + case DbInscriptionIndexResultCountType.all: + total = await this.getChainTipInscriptionCount(); + break; + case DbInscriptionIndexResultCountType.mimeType: + total = await this.getMimeTypeInscriptionCount(filters?.mime_type); + break; + case DbInscriptionIndexResultCountType.satRarity: + total = await this.geSatRarityInscriptionCount(filters?.sat_rarity); + break; + } + return { + total, + results: results ?? [], + }; + }); + } + + async getInscriptionLocations( + args: InscriptionIdentifier & { limit: number; offset: number } + ): Promise> { + const results = await this.sql<({ total: number } & DbLocation)[]>` + SELECT ${this.sql(LOCATIONS_COLUMNS.map(c => `l.${c}`))}, COUNT(*) OVER() as total + FROM locations AS l + INNER JOIN inscriptions AS i ON l.inscription_id = i.id + WHERE + ${ + 'number' in args + ? this.sql`i.number = ${args.number}` + : this.sql`i.genesis_id = ${args.genesis_id}` + } + ORDER BY l.block_height DESC + LIMIT ${args.limit} + OFFSET ${args.offset} + `; + return { + total: results[0]?.total ?? 0, + results: results ?? [], + }; + } + + async getJsonContent(args: InscriptionIdentifier): Promise { + const results = await this.sql` + SELECT ${this.sql(JSON_CONTENTS_COLUMNS.map(c => `j.${c}`))} + FROM json_contents AS j + INNER JOIN inscriptions AS i ON j.inscription_id = i.id + WHERE + ${ + 'number' in args + ? this.sql`i.number = ${args.number}` + : this.sql`i.genesis_id = ${args.genesis_id}` + } + LIMIT 1 + `; + if (results.count === 1) { + return results[0]; + } + } + + async refreshMaterializedView(viewName: string) { + const isProd = process.env.NODE_ENV === 'production'; + await this.sql`REFRESH MATERIALIZED VIEW ${ + isProd ? this.sql`CONCURRENTLY` : this.sql`` + } ${this.sql(viewName)}`; + } + + private async insertInscriptionGenesis(args: { inscription: DbInscriptionInsert; location: DbLocationInsert; - }): Promise { + }): Promise { let inscription_id: number | undefined; await this.sqlWriteTransaction(async sql => { // Are we upserting? @@ -168,12 +520,13 @@ export class PgStore extends BasePgStore { content = EXCLUDED.content `; } - await this.updateChainTipBlockHeight({ blockHeight: args.location.block_height }); }); - if (inscription_id) await this.normalizeInscriptionLocations({ inscription_id }); + return inscription_id; } - async insertInscriptionTransfer(args: { location: DbLocationInsert }): Promise { + private async insertInscriptionTransfer(args: { + location: DbLocationInsert; + }): Promise { let inscription_id: number | undefined; await this.sqlWriteTransaction(async sql => { const inscription = await sql<{ id: number }[]>` @@ -216,15 +569,18 @@ export class PgStore extends BasePgStore { timestamp = EXCLUDED.timestamp `; }); - if (inscription_id) await this.normalizeInscriptionLocations({ inscription_id }); + return inscription_id; } - async rollBackInscriptionGenesis(args: { genesis_id: string }): Promise { + private async rollBackInscriptionGenesis(args: { genesis_id: string }): Promise { // This will cascade into dependent tables. await this.sql`DELETE FROM inscriptions WHERE genesis_id = ${args.genesis_id}`; } - async rollBackInscriptionTransfer(args: { genesis_id: string; output: string }): Promise { + private async rollBackInscriptionTransfer(args: { + genesis_id: string; + output: string; + }): Promise { let inscription_id: number | undefined; await this.sqlWriteTransaction(async sql => { const inscription = await sql<{ id: number }[]>` @@ -240,274 +596,30 @@ export class PgStore extends BasePgStore { WHERE inscription_id = ${inscription_id} AND output = ${args.output} `; }); - if (inscription_id) await this.normalizeInscriptionLocations({ inscription_id }); + return inscription_id; } - private async normalizeInscriptionLocations(args: { inscription_id: number }): Promise { - await this.sql` - WITH i_genesis AS ( - SELECT id FROM locations - WHERE inscription_id = ${args.inscription_id} - ORDER BY block_height ASC - LIMIT 1 - ), i_current AS ( - SELECT id FROM locations - WHERE inscription_id = ${args.inscription_id} - ORDER BY block_height DESC - LIMIT 1 - ) - UPDATE locations SET - current = (CASE WHEN id = (SELECT id FROM i_current) THEN TRUE ELSE FALSE END), - genesis = (CASE WHEN id = (SELECT id FROM i_genesis) THEN TRUE ELSE FALSE END) - WHERE inscription_id = ${args.inscription_id} - `; - } - - async getInscriptionCurrentLocation(args: { output: string }): Promise { - const result = await this.sql` - SELECT ${this.sql(LOCATIONS_COLUMNS)} - FROM locations - WHERE output = ${args.output} - AND current = TRUE - `; - if (result.count === 0) { - return undefined; - } - return result[0]; - } - - async getInscriptionContent( - args: InscriptionIdentifier - ): Promise { - const result = await this.sql` - SELECT content, content_type, content_length - FROM inscriptions - WHERE ${ - 'genesis_id' in args - ? this.sql`genesis_id = ${args.genesis_id}` - : this.sql`number = ${args.number}` - } - `; - if (result.count > 0) { - return result[0]; - } - } - - async getInscriptionETag(args: InscriptionIdentifier): Promise { - const result = await this.sql<{ etag: string }[]>` - SELECT date_part('epoch', l.timestamp)::text AS etag - FROM locations AS l - INNER JOIN inscriptions AS i ON l.inscription_id = i.id - WHERE ${ - 'genesis_id' in args - ? this.sql`i.genesis_id = ${args.genesis_id}` - : this.sql`i.number = ${args.number}` + private async normalizeInscriptionLocations(args: { inscription_id: number[] }): Promise { + await this.sqlWriteTransaction(async sql => { + for (const id of args.inscription_id) { + await sql` + WITH i_genesis AS ( + SELECT id FROM locations + WHERE inscription_id = ${id} + ORDER BY block_height ASC + LIMIT 1 + ), i_current AS ( + SELECT id FROM locations + WHERE inscription_id = ${id} + ORDER BY block_height DESC + LIMIT 1 + ) + UPDATE locations SET + current = (CASE WHEN id = (SELECT id FROM i_current) THEN TRUE ELSE FALSE END), + genesis = (CASE WHEN id = (SELECT id FROM i_genesis) THEN TRUE ELSE FALSE END) + WHERE inscription_id = ${id} + `; } - AND l.current = TRUE - `; - if (result.count > 0) { - return result[0].etag; - } - } - - async getInscriptions( - page: { - limit: number; - offset: number; - }, - args?: { - genesis_id?: string[]; - genesis_block_height?: number; - genesis_block_hash?: string; - from_genesis_block_height?: number; - to_genesis_block_height?: number; - from_genesis_timestamp?: number; - to_genesis_timestamp?: number; - from_sat_coinbase_height?: number; - to_sat_coinbase_height?: number; - number?: number[]; - from_number?: number; - to_number?: number; - address?: string[]; - mime_type?: string[]; - output?: string; - sat_rarity?: SatoshiRarity[]; - sat_ordinal?: bigint; - from_sat_ordinal?: bigint; - to_sat_ordinal?: bigint; - }, - sort?: { - order_by?: OrderBy; - order?: Order; - } - ): Promise> { - // Sanitize ordering args because we'll use `unsafe` to concatenate them into the query. - let orderBy = 'gen.block_height'; - switch (sort?.order_by) { - case OrderBy.ordinal: - orderBy = 'loc.sat_ordinal'; - break; - case OrderBy.rarity: - orderBy = - "ARRAY_POSITION(ARRAY['common','uncommon','rare','epic','legendary','mythic'], loc.sat_rarity)"; - break; - } - const order = sort?.order === Order.asc ? 'ASC' : 'DESC'; - // Do we need a filtered `COUNT(*)`? If not, use the global inscription count from `chain_tip`. - const unfiltered = - args === undefined || Object.values(args).find(k => k !== undefined) === undefined; - - const results = await this.sql<({ total: number } & DbFullyLocatedInscriptionResult)[]>` - SELECT - i.genesis_id, - i.number, - i.mime_type, - i.content_type, - i.content_length, - i.fee AS genesis_fee, - gen.block_height AS genesis_block_height, - gen.block_hash AS genesis_block_hash, - gen.tx_id AS genesis_tx_id, - gen.timestamp AS genesis_timestamp, - gen.address AS genesis_address, - loc.tx_id, - loc.address, - loc.output, - loc.offset, - loc.sat_ordinal, - loc.sat_rarity, - loc.timestamp, - loc.value, - loc.sat_coinbase_height, - ${unfiltered ? '0 as total' : this.sql`COUNT(*) OVER() as total`} - FROM inscriptions AS i - INNER JOIN locations AS loc ON loc.inscription_id = i.id - INNER JOIN locations AS gen ON gen.inscription_id = i.id - WHERE loc.current = TRUE AND gen.genesis = TRUE - ${ - args?.genesis_id?.length - ? this.sql`AND i.genesis_id IN ${this.sql(args.genesis_id)}` - : this.sql`` - } - ${ - args?.genesis_block_height - ? this.sql`AND gen.block_height = ${args.genesis_block_height}` - : this.sql`` - } - ${ - args?.genesis_block_hash - ? this.sql`AND gen.block_hash = ${args.genesis_block_hash}` - : this.sql`` - } - ${ - args?.from_genesis_block_height - ? this.sql`AND gen.block_height >= ${args.from_genesis_block_height}` - : this.sql`` - } - ${ - args?.to_genesis_block_height - ? this.sql`AND gen.block_height <= ${args.to_genesis_block_height}` - : this.sql`` - } - ${ - args?.from_sat_coinbase_height - ? this.sql`AND loc.sat_coinbase_height >= ${args.from_sat_coinbase_height}` - : this.sql`` - } - ${ - args?.to_sat_coinbase_height - ? this.sql`AND loc.sat_coinbase_height <= ${args.to_sat_coinbase_height}` - : this.sql`` - } - ${ - args?.from_genesis_timestamp - ? this.sql`AND gen.timestamp >= to_timestamp(${args.from_genesis_timestamp})` - : this.sql`` - } - ${ - args?.to_genesis_timestamp - ? this.sql`AND gen.timestamp <= to_timestamp(${args.to_genesis_timestamp})` - : this.sql`` - } - ${ - args?.from_sat_ordinal - ? this.sql`AND loc.sat_ordinal >= ${args.from_sat_ordinal}` - : this.sql`` - } - ${ - args?.to_sat_ordinal - ? this.sql`AND loc.sat_ordinal <= ${args.to_sat_ordinal}` - : this.sql`` - } - ${args?.number?.length ? this.sql`AND i.number IN ${this.sql(args.number)}` : this.sql``} - ${args?.from_number ? this.sql`AND i.number >= ${args.from_number}` : this.sql``} - ${args?.to_number ? this.sql`AND i.number <= ${args.to_number}` : this.sql``} - ${ - args?.address?.length - ? this.sql`AND loc.address IN ${this.sql(args.address)}` - : this.sql`` - } - ${ - args?.mime_type?.length - ? this.sql`AND i.mime_type IN ${this.sql(args.mime_type)}` - : this.sql`` - } - ${args?.output ? this.sql`AND loc.output = ${args.output}` : this.sql``} - ${ - args?.sat_rarity?.length - ? this.sql`AND loc.sat_rarity IN ${this.sql(args.sat_rarity)}` - : this.sql`` - } - ${args?.sat_ordinal ? this.sql`AND loc.sat_ordinal = ${args.sat_ordinal}` : this.sql``} - ORDER BY ${this.sql.unsafe(orderBy)} ${this.sql.unsafe(order)} - LIMIT ${page.limit} - OFFSET ${page.offset} - `; - const total = unfiltered ? await this.getChainTipInscriptionCount() : results[0]?.total ?? 0; - return { - total, - results: results ?? [], - }; - } - - async getInscriptionLocations( - args: InscriptionIdentifier & { limit: number; offset: number } - ): Promise> { - const results = await this.sql<({ total: number } & DbLocation)[]>` - SELECT ${this.sql(LOCATIONS_COLUMNS.map(c => `l.${c}`))}, COUNT(*) OVER() as total - FROM locations AS l - INNER JOIN inscriptions AS i ON l.inscription_id = i.id - WHERE - ${ - 'number' in args - ? this.sql`i.number = ${args.number}` - : this.sql`i.genesis_id = ${args.genesis_id}` - } - ORDER BY l.block_height DESC - LIMIT ${args.limit} - OFFSET ${args.offset} - `; - return { - total: results[0]?.total ?? 0, - results: results ?? [], - }; - } - - async getJsonContent(args: InscriptionIdentifier): Promise { - const results = await this.sql` - SELECT ${this.sql(JSON_CONTENTS_COLUMNS.map(c => `j.${c}`))} - FROM json_contents AS j - INNER JOIN inscriptions AS i ON j.inscription_id = i.id - WHERE - ${ - 'number' in args - ? this.sql`i.number = ${args.number}` - : this.sql`i.genesis_id = ${args.genesis_id}` - } - LIMIT 1 - `; - if (results.count === 1) { - return results[0]; - } + }); } } diff --git a/src/pg/types.ts b/src/pg/types.ts index f0ba41c4..ce8b58bc 100644 --- a/src/pg/types.ts +++ b/src/pg/types.ts @@ -1,3 +1,5 @@ +import { Order, OrderBy } from '../api/schemas'; +import { SatoshiRarity } from '../api/util/ordinal-satoshi'; import { OpJson } from './helpers'; import { PgBytea, PgJsonb, PgNumeric } from './postgres-tools/types'; @@ -131,3 +133,47 @@ export type DbJsonContentInsert = { }; export const JSON_CONTENTS_COLUMNS = ['id', 'inscription_id', 'p', 'op', 'content']; + +export type DbInscriptionIndexPaging = { + limit: number; + offset: number; +}; + +export type DbInscriptionIndexFilters = { + genesis_id?: string[]; + genesis_block_height?: number; + genesis_block_hash?: string; + from_genesis_block_height?: number; + to_genesis_block_height?: number; + from_genesis_timestamp?: number; + to_genesis_timestamp?: number; + from_sat_coinbase_height?: number; + to_sat_coinbase_height?: number; + number?: number[]; + from_number?: number; + to_number?: number; + address?: string[]; + mime_type?: string[]; + output?: string; + sat_rarity?: SatoshiRarity[]; + sat_ordinal?: bigint; + from_sat_ordinal?: bigint; + to_sat_ordinal?: bigint; +}; + +export type DbInscriptionIndexOrder = { + order_by?: OrderBy; + order?: Order; +}; + +/** Type of row count required for an inscription index endpoint call */ +export enum DbInscriptionIndexResultCountType { + /** All inscriptions */ + all, + /** Filtered by mime type */ + mimeType, + /** Filtered by sat rarity */ + satRarity, + /** Filtered by custom arguments */ + custom, +} diff --git a/tests/cache.test.ts b/tests/cache.test.ts index c9e0bee1..e63b53bc 100644 --- a/tests/cache.test.ts +++ b/tests/cache.test.ts @@ -1,7 +1,7 @@ import { buildApiServer } from '../src/api/init'; import { cycleMigrations } from '../src/pg/migrations'; import { PgStore } from '../src/pg/pg-store'; -import { TestFastifyServer } from './helpers'; +import { TestChainhookPayloadBuilder, TestFastifyServer } from './helpers'; describe('ETag cache', () => { let db: PgStore; @@ -19,31 +19,27 @@ describe('ETag cache', () => { }); test('inscription cache control', async () => { - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - mime_type: 'image/png', + const block = new TestChainhookPayloadBuilder() + .apply() + .block({ height: 775617 }) + .transaction({ hash: '0x38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc' }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - number: 7, - content: '0x48656C6C6F', - fee: '2805', - }, - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775617, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); + inscription_number: 7, + inscription_fee: 2805, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + }) + .build(); + await db.updateInscriptions(block); const response = await fastify.inject({ method: 'GET', url: '/ordinals/v1/inscriptions/38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', @@ -93,56 +89,48 @@ describe('ETag cache', () => { }); test('inscriptions index cache control', async () => { - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - mime_type: 'text/plain', - content_type: 'text/plain;charset=utf-8', + const block1 = new TestChainhookPayloadBuilder() + .apply() + .block({ height: 778575 }) + .transaction({ hash: '0x9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201' }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain', content_length: 5, - number: 7, - content: '0x48656C6C6F', - fee: '705', - }, - location: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - block_height: 778575, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', - address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', - output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - mime_type: 'image/png', + inscription_number: 7, + inscription_fee: 705, + inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', + inscription_output_value: 10000, + inscriber_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', + }) + .build(); + await db.updateInscriptions(block1); + const block2 = new TestChainhookPayloadBuilder() + .apply() + .block({ height: 775617 }) + .transaction({ hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d' }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - number: 2, - content: '0x48656C6C6F', - fee: '2805', - }, - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775617, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); + inscription_number: 2, + inscription_fee: 2805, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 1676913207, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + }) + .build(); + await db.updateInscriptions(block2); // ETag response const response = await fastify.inject({ @@ -162,22 +150,23 @@ describe('ETag cache', () => { expect(cached.statusCode).toBe(304); // Simulate new location - await db.insertInscriptionTransfer({ - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775618, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a9ff', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - offset: '0', - value: '102', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 20000, - }, - }); + const block3 = new TestChainhookPayloadBuilder() + .apply() + .block({ height: 775618 }) + .transaction({ hash: '0x38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc' }) + .inscriptionTransferred({ + inscription_number: 2, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + ordinal_number: 257418248345364, + updated_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + satpoint_pre_transfer: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + satpoint_post_transfer: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + post_transfer_output_value: 102, + }) + .build(); + await db.updateInscriptions(block3); const cached2 = await fastify.inject({ method: 'GET', url: '/ordinals/v1/inscriptions', diff --git a/tests/helpers.ts b/tests/helpers.ts index 104e0860..ab059308 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -1,6 +1,13 @@ import { FastifyBaseLogger, FastifyInstance } from 'fastify'; import { IncomingMessage, Server, ServerResponse } from 'http'; import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'; +import { + ChainhookPayload, + InscriptionEvent, + InscriptionRevealed, + InscriptionTransferred, + Transaction, +} from '../src/chainhook/schemas'; export type TestFastifyServer = FastifyInstance< Server, @@ -9,3 +16,79 @@ export type TestFastifyServer = FastifyInstance< FastifyBaseLogger, TypeBoxTypeProvider >; + +export class TestChainhookPayloadBuilder { + private payload: ChainhookPayload = { + apply: [], + rollback: [], + chainhook: { + uuid: 'test', + predicate: { + scope: 'ordinals_protocol', + operation: 'inscription_feed', + }, + }, + }; + private action: 'apply' | 'rollback' = 'apply'; + private get lastBlock(): InscriptionEvent { + return this.payload[this.action][this.payload[this.action].length - 1]; + } + private get lastBlockTx(): Transaction { + return this.lastBlock.transactions[this.lastBlock.transactions.length - 1]; + } + + apply(): this { + this.action = 'apply'; + return this; + } + + rollback(): this { + this.action = 'rollback'; + return this; + } + + block(args: { height: number; hash?: string; timestamp?: number }): this { + this.payload[this.action].push({ + block_identifier: { + index: args.height, + hash: args.hash ?? '0x163de66dc9c0949905bfe8e148bde04600223cf88d19f26fdbeba1d6e6fa0f88', + }, + parent_block_identifier: { + index: args.height - 1, + hash: '0x117374e7078440835a744b6b1b13dd2c48c4eff8c58dde07162241a8f15d1e03', + }, + timestamp: args.timestamp ?? 1677803510, + transactions: [], + metadata: {}, + }); + return this; + } + + transaction(args: { hash: string }): this { + this.lastBlock.transactions.push({ + transaction_identifier: { + hash: args.hash, + }, + operations: [], + metadata: { + ordinal_operations: [], + proof: null, + }, + }); + return this; + } + + inscriptionRevealed(args: InscriptionRevealed): this { + this.lastBlockTx.metadata.ordinal_operations.push({ inscription_revealed: args }); + return this; + } + + inscriptionTransferred(args: InscriptionTransferred): this { + this.lastBlockTx.metadata.ordinal_operations.push({ inscription_transferred: args }); + return this; + } + + build(): ChainhookPayload { + return this.payload; + } +} diff --git a/tests/inscriptions.test.ts b/tests/inscriptions.test.ts index 0739f601..9fa680cd 100644 --- a/tests/inscriptions.test.ts +++ b/tests/inscriptions.test.ts @@ -1,7 +1,7 @@ import { buildApiServer } from '../src/api/init'; import { cycleMigrations } from '../src/pg/migrations'; import { PgStore } from '../src/pg/pg-store'; -import { TestFastifyServer } from './helpers'; +import { TestChainhookPayloadBuilder, TestFastifyServer } from './helpers'; describe('/inscriptions', () => { let db: PgStore; @@ -20,56 +20,62 @@ describe('/inscriptions', () => { describe('show', () => { test('shows inscription', async () => { - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - mime_type: 'text/plain', - content_type: 'text/plain;charset=utf-8', - content_length: 5, - number: 188, - content: '0x48656C6C6F', - fee: '705', - }, - location: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - block_height: 778575, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', - address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', - output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - mime_type: 'image/png', - content_type: 'image/png', - content_length: 5, - number: 7, - content: '0x48656C6C6F', - fee: '2805', - }, - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775617, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778575, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '0x9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 188, + inscription_fee: 705, + inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', + inscription_output_value: 10000, + inscriber_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', + }) + .build() + ); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 775617, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '0x38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'image/png', + content_length: 5, + inscription_number: 7, + inscription_fee: 2805, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 257418248345364, + ordinal_block_height: 51483, + ordinal_offset: 0, + satpoint_post_inscription: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + }) + .build() + ); const expected = { address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', genesis_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', @@ -85,7 +91,7 @@ describe('/inscriptions', () => { value: '10000', tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', sat_ordinal: '257418248345364', - sat_coinbase_height: 650000, + sat_coinbase_height: 51483, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', location: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', sat_rarity: 'common', @@ -112,49 +118,56 @@ describe('/inscriptions', () => { }); test('shows correct inscription data after a transfer', async () => { - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - mime_type: 'image/png', - content_type: 'image/png', - content_length: 5, - number: 7, - content: '0x48656C6C6F', - fee: '2805', - }, - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775617, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 775617, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '0x38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'image/png', + content_length: 5, + inscription_number: 7, + inscription_fee: 2805, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 257418248345364, + ordinal_block_height: 51483, + ordinal_offset: 0, + satpoint_post_inscription: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + }) + .build() + ); // Transfer 1 - await db.insertInscriptionTransfer({ - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775700, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7bbbb', - tx_id: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444', - address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', - output: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444:0', - offset: '0', - value: '9000', - timestamp: 1678122360, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ height: 775700, timestamp: 1678122360 }) + .transaction({ + hash: '0xbdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444', + }) + .inscriptionTransferred({ + inscription_number: 7, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + ordinal_number: 257418248345364, + updated_address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + satpoint_pre_transfer: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + satpoint_post_transfer: + 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444:0:0', + post_transfer_output_value: 9000, + }) + .build() + ); const response = await fastify.inject({ method: 'GET', url: '/ordinals/v1/inscriptions/38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', @@ -175,7 +188,7 @@ describe('/inscriptions', () => { value: '9000', tx_id: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444', sat_ordinal: '257418248345364', - sat_coinbase_height: 650000, + sat_coinbase_height: 51483, output: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444:0', location: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444:0:0', sat_rarity: 'common', @@ -185,22 +198,26 @@ describe('/inscriptions', () => { }); // Transfer 2 - await db.insertInscriptionTransfer({ - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775701, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7cccc', - tx_id: 'e3af144354367de58c675e987febcb49f17d6c19e645728b833fe95408feab85', - address: 'bc1pkjq7cerr6h53qm86k9t3dq0gqg8lcfz5jx7z4aj2mpqrjggrnass0u7qqj', - output: 'e3af144354367de58c675e987febcb49f17d6c19e645728b833fe95408feab85:0', - offset: '0', - value: '8000', - timestamp: 1678124000, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ height: 775701, timestamp: 1678124000 }) + .transaction({ + hash: '0xe3af144354367de58c675e987febcb49f17d6c19e645728b833fe95408feab85', + }) + .inscriptionTransferred({ + inscription_number: 7, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + ordinal_number: 257418248345364, + updated_address: 'bc1pkjq7cerr6h53qm86k9t3dq0gqg8lcfz5jx7z4aj2mpqrjggrnass0u7qqj', + satpoint_pre_transfer: + 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444:0:0', + satpoint_post_transfer: + 'e3af144354367de58c675e987febcb49f17d6c19e645728b833fe95408feab85:0:0', + post_transfer_output_value: 8000, + }) + .build() + ); const response2 = await fastify.inject({ method: 'GET', url: '/ordinals/v1/inscriptions/38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', @@ -221,7 +238,7 @@ describe('/inscriptions', () => { tx_id: 'e3af144354367de58c675e987febcb49f17d6c19e645728b833fe95408feab85', value: '8000', sat_ordinal: '257418248345364', - sat_coinbase_height: 650000, + sat_coinbase_height: 51483, output: 'e3af144354367de58c675e987febcb49f17d6c19e645728b833fe95408feab85:0', location: 'e3af144354367de58c675e987febcb49f17d6c19e645728b833fe95408feab85:0:0', sat_rarity: 'common', @@ -232,51 +249,62 @@ describe('/inscriptions', () => { }); }); - describe('history', () => { + describe('transfers', () => { test('shows inscription history after a transfer', async () => { - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - mime_type: 'image/png', - content_type: 'image/png', - content_length: 5, - number: 7, - content: '0x48656C6C6F', - fee: '2805', - }, - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775617, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 775617, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '0x38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'image/png', + content_length: 5, + inscription_number: 7, + inscription_fee: 2805, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + }) + .build() + ); // Transfer 1 - await db.insertInscriptionTransfer({ - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775700, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7bbbb', - tx_id: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444', - address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', - output: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444:0', - offset: '0', - value: '9000', - timestamp: 1678122360, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 775700, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7bbbb', + timestamp: 1678122360, + }) + .transaction({ + hash: '0xbdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444', + }) + .inscriptionTransferred({ + inscription_number: 7, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + ordinal_number: 257418248345364, + updated_address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + satpoint_pre_transfer: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + satpoint_post_transfer: + 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444:0:0', + post_transfer_output_value: 9000, + }) + .build() + ); const response = await fastify.inject({ method: 'GET', url: '/ordinals/v1/inscriptions/38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0/transfers', @@ -310,22 +338,30 @@ describe('/inscriptions', () => { ]); // Transfer 2 - await db.insertInscriptionTransfer({ - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775701, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7cccc', - tx_id: 'e3af144354367de58c675e987febcb49f17d6c19e645728b833fe95408feab85', - address: 'bc1pkjq7cerr6h53qm86k9t3dq0gqg8lcfz5jx7z4aj2mpqrjggrnass0u7qqj', - output: 'e3af144354367de58c675e987febcb49f17d6c19e645728b833fe95408feab85:0', - offset: '0', - value: '8000', - timestamp: 1678124000, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 775701, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7cccc', + timestamp: 1678124000, + }) + .transaction({ + hash: '0xe3af144354367de58c675e987febcb49f17d6c19e645728b833fe95408feab85', + }) + .inscriptionTransferred({ + inscription_number: 7, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + ordinal_number: 257418248345364, + updated_address: 'bc1pkjq7cerr6h53qm86k9t3dq0gqg8lcfz5jx7z4aj2mpqrjggrnass0u7qqj', + satpoint_pre_transfer: + 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444:0:0', + satpoint_post_transfer: + 'e3af144354367de58c675e987febcb49f17d6c19e645728b833fe95408feab85:0:0', + post_transfer_output_value: 8000, + }) + .build() + ); const response2 = await fastify.inject({ method: 'GET', url: '/ordinals/v1/inscriptions/38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0/transfers', @@ -374,56 +410,62 @@ describe('/inscriptions', () => { describe('index', () => { describe('filters', () => { test('index filtered by mime type', async () => { - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - mime_type: 'text/plain', - content_type: 'text/plain;charset=utf-8', - content_length: 5, - number: 7, - content: '0x48656C6C6F', - fee: '705', - }, - location: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - block_height: 778575, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', - address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', - output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - mime_type: 'image/png', - content_type: 'image/png', - content_length: 5, - number: 1, - content: '0x48656C6C6F', - fee: '2805', - }, - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775617, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778575, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 7, + inscription_fee: 705, + inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', + inscription_output_value: 10000, + inscriber_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', + }) + .build() + ); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 775617, + hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'image/png', + content_length: 5, + inscription_number: 1, + inscription_fee: 2805, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + }) + .build() + ); const response1 = await fastify.inject({ method: 'GET', @@ -446,7 +488,7 @@ describe('/inscriptions', () => { number: 7, value: '10000', sat_ordinal: '257418248345364', - sat_coinbase_height: 650000, + sat_coinbase_height: 51483, output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', location: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', sat_rarity: 'common', @@ -479,7 +521,7 @@ describe('/inscriptions', () => { value: '10000', sat_ordinal: '257418248345364', tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - sat_coinbase_height: 650000, + sat_coinbase_height: 51483, output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', location: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', sat_rarity: 'common', @@ -501,56 +543,62 @@ describe('/inscriptions', () => { }); test('index filtered by sat rarity', async () => { - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - mime_type: 'text/plain', - content_type: 'text/plain;charset=utf-8', - content_length: 5, - number: 7, - content: '0x48656C6C6F', - fee: '705', - }, - location: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - block_height: 778575, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', - address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', - output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - mime_type: 'image/png', - content_type: 'image/png', - content_length: 5, - number: 8, - content: '0x48656C6C6F', - fee: '2805', - }, - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775617, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'epic', - sat_coinbase_height: 650000, - }, - }); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778575, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 7, + inscription_fee: 705, + inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', + inscription_output_value: 10000, + inscriber_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', + }) + .build() + ); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 775617, + hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'image/png', + content_length: 5, + inscription_number: 1, + inscription_fee: 2805, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 0, + ordinal_block_height: 0, + ordinal_offset: 0, + satpoint_post_inscription: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + }) + .build() + ); const response1 = await fastify.inject({ method: 'GET', @@ -563,16 +611,16 @@ describe('/inscriptions', () => { const response2 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions?rarity=epic', + url: '/ordinals/v1/inscriptions?rarity=mythic', }); expect(response2.statusCode).toBe(200); const responseJson2 = response2.json(); expect(responseJson2.total).toBe(1); - expect(responseJson2.results[0].sat_rarity).toBe('epic'); + expect(responseJson2.results[0].sat_rarity).toBe('mythic'); const response3 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions?rarity=epic&rarity=common', + url: '/ordinals/v1/inscriptions?rarity=mythic&rarity=common', }); expect(response3.statusCode).toBe(200); const responseJson3 = response3.json(); @@ -580,56 +628,62 @@ describe('/inscriptions', () => { }); test('index filtered by inscription id', async () => { - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - mime_type: 'text/plain', - content_type: 'text/plain;charset=utf-8', - content_length: 5, - number: 7, - content: '0x48656C6C6F', - fee: '705', - }, - location: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - block_height: 778575, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', - address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', - output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - mime_type: 'image/png', - content_type: 'image/png', - content_length: 5, - number: 8, - content: '0x48656C6C6F', - fee: '2805', - }, - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775617, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'epic', - sat_coinbase_height: 650000, - }, - }); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778575, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 7, + inscription_fee: 705, + inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', + inscription_output_value: 10000, + inscriber_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', + }) + .build() + ); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 775617, + hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'image/png', + content_length: 5, + inscription_number: 1, + inscription_fee: 2805, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + }) + .build() + ); const response1 = await fastify.inject({ method: 'GET', @@ -663,56 +717,62 @@ describe('/inscriptions', () => { }); test('index filtered by inscription number', async () => { - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - mime_type: 'text/plain', - content_type: 'text/plain;charset=utf-8', - content_length: 5, - number: 7, - content: '0x48656C6C6F', - fee: '705', - }, - location: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - block_height: 778575, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', - address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', - output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - mime_type: 'image/png', - content_type: 'image/png', - content_length: 5, - number: 50, - content: '0x48656C6C6F', - fee: '2805', - }, - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775617, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'epic', - sat_coinbase_height: 650000, - }, - }); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778575, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 7, + inscription_fee: 705, + inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', + inscription_output_value: 10000, + inscriber_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', + }) + .build() + ); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 775617, + hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'image/png', + content_length: 5, + inscription_number: 50, + inscription_fee: 2805, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + }) + .build() + ); const response1 = await fastify.inject({ method: 'GET', @@ -742,56 +802,62 @@ describe('/inscriptions', () => { }); test('index filtered by block height', async () => { - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - mime_type: 'text/plain', - content_type: 'text/plain;charset=utf-8', - content_length: 5, - number: 7, - content: '0x48656C6C6F', - fee: '705', - }, - location: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - block_height: 778575, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', - address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', - output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - mime_type: 'image/png', - content_type: 'image/png', - content_length: 5, - number: 8, - content: '0x48656C6C6F', - fee: '2805', - }, - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775617, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'epic', - sat_coinbase_height: 650000, - }, - }); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778575, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 7, + inscription_fee: 705, + inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', + inscription_output_value: 10000, + inscriber_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', + }) + .build() + ); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 775617, + hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'image/png', + content_length: 5, + inscription_number: 1, + inscription_fee: 2805, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + }) + .build() + ); const response1 = await fastify.inject({ method: 'GET', @@ -822,56 +888,62 @@ describe('/inscriptions', () => { }); test('index filtered by block hash', async () => { - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - mime_type: 'text/plain', - content_type: 'text/plain;charset=utf-8', - content_length: 5, - number: 7, - content: '0x48656C6C6F', - fee: '705', - }, - location: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - block_height: 778575, - block_hash: '000000000000000000039b3051705a16fcf310a70dee55742339e6da70181bf7', - tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', - address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', - output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - mime_type: 'image/png', - content_type: 'image/png', - content_length: 5, - number: 8, - content: '0x48656C6C6F', - fee: '2805', - }, - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775617, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'epic', - sat_coinbase_height: 650000, - }, - }); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778575, + hash: '000000000000000000039b3051705a16fcf310a70dee55742339e6da70181bf7', + timestamp: 1676913207, + }) + .transaction({ + hash: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 7, + inscription_fee: 705, + inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', + inscription_output_value: 10000, + inscriber_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', + }) + .build() + ); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 775617, + hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'image/png', + content_length: 5, + inscription_number: 1, + inscription_fee: 2805, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + }) + .build() + ); const response1 = await fastify.inject({ method: 'GET', @@ -886,56 +958,62 @@ describe('/inscriptions', () => { }); test('index filtered by timestamp range', async () => { - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - mime_type: 'text/plain', - content_type: 'text/plain;charset=utf-8', - content_length: 5, - number: 7, - content: '0x48656C6C6F', - fee: '705', - }, - location: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - block_height: 778575, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', - address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', - output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', - offset: '0', - value: '10000', - timestamp: 1677731361, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - mime_type: 'image/png', - content_type: 'image/png', - content_length: 5, - number: 8, - content: '0x48656C6C6F', - fee: '2805', - }, - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775617, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - offset: '0', - value: '10000', - timestamp: 1675312161, - sat_ordinal: '257418248345364', - sat_rarity: 'epic', - sat_coinbase_height: 650000, - }, - }); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778575, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1677731361, + }) + .transaction({ + hash: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 7, + inscription_fee: 705, + inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', + inscription_output_value: 10000, + inscriber_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', + }) + .build() + ); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 775617, + hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1675312161, + }) + .transaction({ + hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'image/png', + content_length: 5, + inscription_number: 1, + inscription_fee: 2805, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + }) + .build() + ); const response2 = await fastify.inject({ method: 'GET', @@ -957,56 +1035,62 @@ describe('/inscriptions', () => { }); test('index filtered by sat ordinal range', async () => { - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - mime_type: 'text/plain', - content_type: 'text/plain;charset=utf-8', - content_length: 5, - number: 7, - content: '0x48656C6C6F', - fee: '705', - }, - location: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - block_height: 778575, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', - address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', - output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', - offset: '0', - value: '10000', - timestamp: 1677731361, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - mime_type: 'image/png', - content_type: 'image/png', - content_length: 5, - number: 8, - content: '0x48656C6C6F', - fee: '2805', - }, - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775617, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - offset: '0', - value: '10000', - timestamp: 1675312161, - sat_ordinal: '1000000000000', - sat_rarity: 'epic', - sat_coinbase_height: 650000, - }, - }); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778575, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1677731361, + }) + .transaction({ + hash: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 7, + inscription_fee: 705, + inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', + inscription_output_value: 10000, + inscriber_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', + }) + .build() + ); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 775617, + hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1675312161, + }) + .transaction({ + hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'image/png', + content_length: 5, + inscription_number: 1, + inscription_fee: 2805, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 1000000000000, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + }) + .build() + ); const response2 = await fastify.inject({ method: 'GET', @@ -1028,127 +1112,139 @@ describe('/inscriptions', () => { }); test('index filtered by sat coinbase height range', async () => { - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - mime_type: 'text/plain', - content_type: 'text/plain;charset=utf-8', - content_length: 5, - number: 7, - content: '0x48656C6C6F', - fee: '705', - }, - location: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - block_height: 778575, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', - address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', - output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', - offset: '0', - value: '10000', - timestamp: 1677731361, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - mime_type: 'image/png', - content_type: 'image/png', - content_length: 5, - number: 8, - content: '0x48656C6C6F', - fee: '2805', - }, - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775617, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - offset: '0', - value: '10000', - timestamp: 1675312161, - sat_ordinal: '1000000000000', - sat_rarity: 'epic', - sat_coinbase_height: 750000, - }, - }); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778575, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1677731361, + }) + .transaction({ + hash: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 7, + inscription_fee: 705, + inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', + inscription_output_value: 10000, + inscriber_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + ordinal_number: 257418248345364, + ordinal_block_height: 51483, + ordinal_offset: 0, + satpoint_post_inscription: + '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', + }) + .build() + ); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 775617, + hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1675312161, + }) + .transaction({ + hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'image/png', + content_length: 5, + inscription_number: 1, + inscription_fee: 2805, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 1000000000000, + ordinal_block_height: 200, + ordinal_offset: 0, + satpoint_post_inscription: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + }) + .build() + ); const response2 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions?from_sat_coinbase_height=655000', + url: '/ordinals/v1/inscriptions?from_sat_coinbase_height=51400', }); expect(response2.statusCode).toBe(200); const responseJson2 = response2.json(); expect(responseJson2.total).toBe(1); - expect(responseJson2.results[0].sat_coinbase_height).toBe(750000); + expect(responseJson2.results[0].sat_coinbase_height).toBe(51483); const response3 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions?to_sat_coinbase_height=655000', + url: '/ordinals/v1/inscriptions?to_sat_coinbase_height=51400', }); expect(response3.statusCode).toBe(200); const responseJson3 = response3.json(); expect(responseJson3.total).toBe(1); - expect(responseJson3.results[0].sat_coinbase_height).toBe(650000); + expect(responseJson3.results[0].sat_coinbase_height).toBe(200); }); test('index filtered by inscription number range', async () => { - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - mime_type: 'text/plain', - content_type: 'text/plain;charset=utf-8', - content_length: 5, - number: 7, - content: '0x48656C6C6F', - fee: '705', - }, - location: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - block_height: 778575, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', - address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', - output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', - offset: '0', - value: '10000', - timestamp: 1677731361, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - mime_type: 'image/png', - content_type: 'image/png', - content_length: 5, - number: 50, - content: '0x48656C6C6F', - fee: '2805', - }, - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775617, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - offset: '0', - value: '10000', - timestamp: 1675312161, - sat_ordinal: '1000000000000', - sat_rarity: 'epic', - sat_coinbase_height: 750000, - }, - }); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778575, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1677731361, + }) + .transaction({ + hash: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 7, + inscription_fee: 705, + inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', + inscription_output_value: 10000, + inscriber_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', + }) + .build() + ); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 775617, + hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1675312161, + }) + .transaction({ + hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'image/png', + content_length: 5, + inscription_number: 50, + inscription_fee: 2805, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 1000000000000, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + }) + .build() + ); const response2 = await fastify.inject({ method: 'GET', @@ -1170,56 +1266,62 @@ describe('/inscriptions', () => { }); test('index filtered by output', async () => { - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - mime_type: 'text/plain', - content_type: 'text/plain;charset=utf-8', - content_length: 5, - number: 7, - content: '0x48656C6C6F', - fee: '705', - }, - location: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - block_height: 778575, - block_hash: '000000000000000000039b3051705a16fcf310a70dee55742339e6da70181bf7', - tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', - address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', - output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - mime_type: 'image/png', - content_type: 'image/png', - content_length: 5, - number: 8, - content: '0x48656C6C6F', - fee: '2805', - }, - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775617, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'epic', - sat_coinbase_height: 650000, - }, - }); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778575, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1677731361, + }) + .transaction({ + hash: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 7, + inscription_fee: 705, + inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', + inscription_output_value: 10000, + inscriber_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', + }) + .build() + ); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 775617, + hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1675312161, + }) + .transaction({ + hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'image/png', + content_length: 5, + inscription_number: 1, + inscription_fee: 2805, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 1000000000000, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + }) + .build() + ); const response1 = await fastify.inject({ method: 'GET', @@ -1234,56 +1336,62 @@ describe('/inscriptions', () => { }); test('index filtered by address', async () => { - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - mime_type: 'text/plain', - content_type: 'text/plain;charset=utf-8', - content_length: 5, - number: 7, - content: '0x48656C6C6F', - fee: '705', - }, - location: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - block_height: 778575, - block_hash: '000000000000000000039b3051705a16fcf310a70dee55742339e6da70181bf7', - tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', - address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', - output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - mime_type: 'image/png', - content_type: 'image/png', - content_length: 5, - number: 8, - content: '0x48656C6C6F', - fee: '2805', - }, - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775617, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'epic', - sat_coinbase_height: 650000, - }, - }); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778575, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1677731361, + }) + .transaction({ + hash: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 7, + inscription_fee: 705, + inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', + inscription_output_value: 10000, + inscriber_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', + }) + .build() + ); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 775617, + hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1675312161, + }) + .transaction({ + hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'image/png', + content_length: 5, + inscription_number: 1, + inscription_fee: 2805, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 1000000000000, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + }) + .build() + ); const response1 = await fastify.inject({ method: 'GET', @@ -1308,82 +1416,90 @@ describe('/inscriptions', () => { describe('ordering', () => { test('index ordered by sat rarity', async () => { - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - mime_type: 'text/plain', - content_type: 'text/plain;charset=utf-8', - content_length: 5, - content: '0x48656C6C6F', - number: 7, - fee: '705', - }, - location: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - block_height: 778575, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', - address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', - output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - mime_type: 'image/png', - content_type: 'image/png', - content_length: 5, - number: 8, - content: '0x48656C6C6F', - fee: '2805', - }, - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775617, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'epic', - sat_coinbase_height: 650000, - }, - }); - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1i0', - mime_type: 'image/png', - content_type: 'image/png', - content_length: 5, - number: 9, - content: '0x48656C6C6F', - fee: '2805', - }, - location: { - genesis_id: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1i0', - block_height: 778583, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1', - address: 'bc1pxq6t85qp57aw8yf8eh9t7vsgd9zm5a8372rdll5jzrmc3cxqdpmqfucdry', - output: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '0', - sat_rarity: 'mythic', - sat_coinbase_height: 650000, - }, - }); - await db.updateChainTipInscriptionCount(); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778575, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 7, + inscription_fee: 705, + inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', + inscription_output_value: 10000, + inscriber_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', + }) + .build() + ); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 775617, + hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 8, + inscription_fee: 705, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 1050000000000000, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + }) + .build() + ); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778583, + hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'image/png', + content_length: 5, + inscription_number: 9, + inscription_fee: 2805, + inscription_id: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1i0', + inscription_output_value: 10000, + inscriber_address: 'bc1pxq6t85qp57aw8yf8eh9t7vsgd9zm5a8372rdll5jzrmc3cxqdpmqfucdry', + ordinal_number: 0, + ordinal_block_height: 0, + ordinal_offset: 0, + satpoint_post_inscription: + '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1:0:0', + }) + .build() + ); const response1 = await fastify.inject({ method: 'GET', @@ -1409,82 +1525,90 @@ describe('/inscriptions', () => { }); test('index ordered by sat ordinal', async () => { - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - mime_type: 'text/plain', - content_type: 'text/plain;charset=utf-8', - content_length: 5, - number: 7, - content: '0x48656C6C6F', - fee: '705', - }, - location: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - block_height: 778575, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', - address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', - output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '3', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - mime_type: 'image/png', - content_type: 'image/png', - content_length: 5, - number: 8, - content: '0x48656C6C6F', - fee: '2805', - }, - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775617, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '5', - sat_rarity: 'epic', - sat_coinbase_height: 650000, - }, - }); - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1i0', - mime_type: 'image/png', - content_type: 'image/png', - content_length: 5, - number: 9, - content: '0x48656C6C6F', - fee: '2805', - }, - location: { - genesis_id: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1i0', - block_height: 778583, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1', - address: 'bc1pxq6t85qp57aw8yf8eh9t7vsgd9zm5a8372rdll5jzrmc3cxqdpmqfucdry', - output: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '0', - sat_rarity: 'mythic', - sat_coinbase_height: 650000, - }, - }); - await db.updateChainTipInscriptionCount(); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778575, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 7, + inscription_fee: 705, + inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', + inscription_output_value: 10000, + inscriber_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', + }) + .build() + ); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 775617, + hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 8, + inscription_fee: 705, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 1050000000000000, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + }) + .build() + ); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778583, + hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'image/png', + content_length: 5, + inscription_number: 9, + inscription_fee: 2805, + inscription_id: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1i0', + inscription_output_value: 10000, + inscriber_address: 'bc1pxq6t85qp57aw8yf8eh9t7vsgd9zm5a8372rdll5jzrmc3cxqdpmqfucdry', + ordinal_number: 0, + ordinal_block_height: 0, + ordinal_offset: 0, + satpoint_post_inscription: + '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1:0:0', + }) + .build() + ); const response1 = await fastify.inject({ method: 'GET', @@ -1494,8 +1618,8 @@ describe('/inscriptions', () => { const responseJson1 = response1.json(); expect(responseJson1.total).toBe(3); expect(responseJson1.results[0].sat_ordinal).toStrictEqual('0'); - expect(responseJson1.results[1].sat_ordinal).toStrictEqual('3'); - expect(responseJson1.results[2].sat_ordinal).toStrictEqual('5'); + expect(responseJson1.results[1].sat_ordinal).toStrictEqual('257418248345364'); + expect(responseJson1.results[2].sat_ordinal).toStrictEqual('1050000000000000'); const response2 = await fastify.inject({ method: 'GET', @@ -1504,88 +1628,96 @@ describe('/inscriptions', () => { expect(response2.statusCode).toBe(200); const responseJson2 = response2.json(); expect(responseJson2.total).toBe(3); - expect(responseJson2.results[0].sat_ordinal).toStrictEqual('5'); - expect(responseJson2.results[1].sat_ordinal).toStrictEqual('3'); + expect(responseJson2.results[0].sat_ordinal).toStrictEqual('1050000000000000'); + expect(responseJson2.results[1].sat_ordinal).toStrictEqual('257418248345364'); expect(responseJson2.results[2].sat_ordinal).toStrictEqual('0'); }); test('index ordered by genesis block height', async () => { - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - mime_type: 'text/plain', - content_type: 'text/plain;charset=utf-8', - content_length: 5, - number: 7, - content: '0x48656C6C6F', - fee: '705', - }, - location: { - genesis_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - block_height: 778575, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', - address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', - output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '3', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - mime_type: 'image/png', - content_type: 'image/png', - content_length: 5, - number: 8, - content: '0x48656C6C6F', - fee: '2805', - }, - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775617, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '5', - sat_rarity: 'epic', - sat_coinbase_height: 650000, - }, - }); - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1i0', - mime_type: 'image/png', - content_type: 'image/png', - content_length: 5, - number: 9, - content: '0x48656C6C6F', - fee: '2805', - }, - location: { - genesis_id: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1i0', - block_height: 778583, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1', - address: 'bc1pxq6t85qp57aw8yf8eh9t7vsgd9zm5a8372rdll5jzrmc3cxqdpmqfucdry', - output: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '0', - sat_rarity: 'mythic', - sat_coinbase_height: 650000, - }, - }); - await db.updateChainTipInscriptionCount(); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778575, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 7, + inscription_fee: 705, + inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', + inscription_output_value: 10000, + inscriber_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', + }) + .build() + ); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 775617, + hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 8, + inscription_fee: 705, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 1050000000000000, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + }) + .build() + ); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778583, + hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'image/png', + content_length: 5, + inscription_number: 9, + inscription_fee: 2805, + inscription_id: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1i0', + inscription_output_value: 10000, + inscriber_address: 'bc1pxq6t85qp57aw8yf8eh9t7vsgd9zm5a8372rdll5jzrmc3cxqdpmqfucdry', + ordinal_number: 0, + ordinal_block_height: 0, + ordinal_offset: 0, + satpoint_post_inscription: + '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1:0:0', + }) + .build() + ); const response1 = await fastify.inject({ method: 'GET', diff --git a/tests/sats.test.ts b/tests/sats.test.ts index 99cff5a1..88d03fac 100644 --- a/tests/sats.test.ts +++ b/tests/sats.test.ts @@ -1,7 +1,7 @@ import { buildApiServer } from '../src/api/init'; import { cycleMigrations } from '../src/pg/migrations'; import { PgStore } from '../src/pg/pg-store'; -import { TestFastifyServer } from './helpers'; +import { TestChainhookPayloadBuilder, TestFastifyServer } from './helpers'; describe('/sats', () => { let db: PgStore; @@ -39,31 +39,28 @@ describe('/sats', () => { }); test('returns sat with inscription', async () => { - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - mime_type: 'image/png', - content_type: 'image/png', - content_length: 5, - number: 7, - content: '0x48656C6C6F', - fee: '2805', - }, - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775617, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ height: 775617 }) + .transaction({ hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc' }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'image/png', + content_length: 5, + inscription_number: 7, + inscription_fee: 2805, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + }) + .build() + ); const response = await fastify.inject({ method: 'GET', url: '/ordinals/v1/sats/257418248345364', diff --git a/tests/server.test.ts b/tests/server.test.ts index 36b7229d..5f53527b 100644 --- a/tests/server.test.ts +++ b/tests/server.test.ts @@ -3,7 +3,7 @@ import { buildChainhookServer, CHAINHOOK_BASE_PATH, PREDICATE_UUID } from '../sr import { ENV } from '../src/env'; import { cycleMigrations } from '../src/pg/migrations'; import { PgStore } from '../src/pg/pg-store'; -import { TestFastifyServer } from './helpers'; +import { TestChainhookPayloadBuilder, TestFastifyServer } from './helpers'; describe('EventServer', () => { let db: PgStore; @@ -233,31 +233,34 @@ describe('EventServer', () => { }); test('parses inscription_transferred apply and rollback', async () => { - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - mime_type: 'image/png', - content_type: 'image/png', - content_length: 5, - number: 7, - content: '0x48656C6C6F', - fee: '2805', - }, - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775617, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '5', - sat_rarity: 'common', - sat_coinbase_height: 0, - }, - }); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 775617, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '0x38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'image/png', + content_length: 5, + inscription_number: 7, + inscription_fee: 2805, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 5, + ordinal_block_height: 0, + ordinal_offset: 0, + satpoint_post_inscription: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + }) + .build() + ); const transfer = { block_identifier: { index: 775618, diff --git a/tests/status.test.ts b/tests/status.test.ts index 49956b75..e1daec03 100644 --- a/tests/status.test.ts +++ b/tests/status.test.ts @@ -1,7 +1,7 @@ import { buildApiServer } from '../src/api/init'; import { cycleMigrations } from '../src/pg/migrations'; import { PgStore } from '../src/pg/pg-store'; -import { TestFastifyServer } from './helpers'; +import { TestChainhookPayloadBuilder, TestFastifyServer } from './helpers'; describe('Status', () => { let db: PgStore; @@ -32,31 +32,28 @@ describe('Status', () => { }); test('returns inscriptions total', async () => { - await db.insertInscriptionGenesis({ - inscription: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - mime_type: 'image/png', - content_type: 'image/png', - content_length: 5, - number: 7, - content: '0x48656C6C6F', - fee: '2805', - }, - location: { - genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - block_height: 775617, - block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - offset: '0', - value: '10000', - timestamp: 1676913207, - sat_ordinal: '257418248345364', - sat_rarity: 'common', - sat_coinbase_height: 650000, - }, - }); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ height: 775617 }) + .transaction({ hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc' }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 7, + inscription_fee: 2805, + inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + }) + .build() + ); const response = await fastify.inject({ method: 'GET', url: '/ordinals/v1/' }); const json = response.json();