diff --git a/CHANGELOG.md b/CHANGELOG.md index d452b3d1..4e9edf40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,148 @@ +## [2.0.0-beta.20](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.19...v2.0.0-beta.20) (2023-11-20) + + +### Bug Fixes + +* also remove null bytes in blessed inscriptions ([7e8d412](https://github.com/hirosystems/ordinals-api/commit/7e8d4127bc4b27917c59349abe9f5fbf98da98ab)) + +## [2.0.0-beta.19](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.18...v2.0.0-beta.19) (2023-11-20) + + +### Bug Fixes + +* remove null bytes before inserting inscriptions ([#274](https://github.com/hirosystems/ordinals-api/issues/274)) ([cec3de0](https://github.com/hirosystems/ordinals-api/commit/cec3de061a5b475161b2d11369fc13b63ad8bc27)) + +## [2.0.0-beta.18](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.17...v2.0.0-beta.18) (2023-11-17) + + +### Bug Fixes + +* address location counts ([#273](https://github.com/hirosystems/ordinals-api/issues/273)) ([86efc3a](https://github.com/hirosystems/ordinals-api/commit/86efc3acf8d743b22edc11e137458a51c892c1c7)) + +## [2.0.0-beta.17](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.16...v2.0.0-beta.17) (2023-11-17) + + +### Bug Fixes + +* inscription count generation ([13e1f16](https://github.com/hirosystems/ordinals-api/commit/13e1f16f6980b6743abe8d95644703329f07fe3f)) + +## [2.0.0-beta.16](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.15...v2.0.0-beta.16) (2023-11-12) + + +### Bug Fixes + +* try returning brc20 batch to normal, use batchIterate everywhere ([1d3a24b](https://github.com/hirosystems/ordinals-api/commit/1d3a24b8bb1d852eb1ae198d84a5940eec997ded)) + +## [2.0.0-beta.15](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.14...v2.0.0-beta.15) (2023-11-12) + + +### Bug Fixes + +* batch size to 2000 ([9cf9230](https://github.com/hirosystems/ordinals-api/commit/9cf9230fe193a2075a0cd2e45a5fa316ecc225c5)) + +## [2.0.0-beta.14](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.13...v2.0.0-beta.14) (2023-11-12) + + +### Bug Fixes + +* try batch iterator generator ([e2ed039](https://github.com/hirosystems/ordinals-api/commit/e2ed039120958fde07297c6760022770dab710a8)) + +## [2.0.0-beta.13](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.12...v2.0.0-beta.13) (2023-11-12) + + +### Bug Fixes + +* reduce brc-20 batch to 5k ([2037647](https://github.com/hirosystems/ordinals-api/commit/20376470455a0887d81f3128047d330e7d97e438)) + +## [2.0.0-beta.12](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.11...v2.0.0-beta.12) (2023-11-12) + + +### Bug Fixes + +* reduce chunk size to 3000 ([9556148](https://github.com/hirosystems/ordinals-api/commit/95561481a140685d27e68c0c4e03d879882354f4)) + +## [2.0.0-beta.11](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.10...v2.0.0-beta.11) (2023-11-12) + + +### Bug Fixes + +* bump docker image ([08688da](https://github.com/hirosystems/ordinals-api/commit/08688da84e7695156aa0e822c6f264e9085c5f1a)) + +## [2.0.0-beta.10](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.9...v2.0.0-beta.10) (2023-11-11) + + +### Bug Fixes + +* reduce brc20 processing batch size to half ([7079dc0](https://github.com/hirosystems/ordinals-api/commit/7079dc079bfec5fbde7018dacbf20c1701d7ccb9)) + +## [2.0.0-beta.9](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.8...v2.0.0-beta.9) (2023-11-04) + + +### Bug Fixes + +* guarantee gap detection is comparing numbers ([0f3f51a](https://github.com/hirosystems/ordinals-api/commit/0f3f51a0186dd2f677e21669d2359cba02122acf)) + +## [2.0.0-beta.8](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.7...v2.0.0-beta.8) (2023-11-03) + + +### Bug Fixes + +* do not reject unbounded inscriptions that come in disorder ([#264](https://github.com/hirosystems/ordinals-api/issues/264)) ([56e2235](https://github.com/hirosystems/ordinals-api/commit/56e2235a3823a94493bbe978c2f8e5be48c6cf46)) + +## [2.0.0-beta.7](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.6...v2.0.0-beta.7) (2023-11-02) + + +### Bug Fixes + +* reduce to 4000 chunk size ([3373422](https://github.com/hirosystems/ordinals-api/commit/337342251987d546600fe331154b3e8cd38dcd18)) + +## [2.0.0-beta.6](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.5...v2.0.0-beta.6) (2023-11-02) + + +### Bug Fixes + +* only scan BRC-20 after its genesis height ([#263](https://github.com/hirosystems/ordinals-api/issues/263)) ([6381760](https://github.com/hirosystems/ordinals-api/commit/6381760f3860a96c3abfd485b612cd212927dca4)) +* reduce batching to 4500 ([8fd770b](https://github.com/hirosystems/ordinals-api/commit/8fd770bc4e83be6ddaa4fdead1d1be55c42f870b)) + +## [2.0.0-beta.5](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.4...v2.0.0-beta.5) (2023-11-02) + + +### Bug Fixes + +* only advance blessed number after gap detection ([c8f3c81](https://github.com/hirosystems/ordinals-api/commit/c8f3c81a657cd28283bdd89e0a17af06cad93d63)) + +## [2.0.0-beta.4](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.3...v2.0.0-beta.4) (2023-11-02) + + +### Bug Fixes + +* only validate gaps for blessed numbers ([#262](https://github.com/hirosystems/ordinals-api/issues/262)) ([29aaeda](https://github.com/hirosystems/ordinals-api/commit/29aaeda39527c88b63b7c82739d8ed858c48a74b)) + +## [2.0.0-beta.3](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.2...v2.0.0-beta.3) (2023-11-02) + + +### Bug Fixes + +* keep block transfer index ([#260](https://github.com/hirosystems/ordinals-api/issues/260)) ([0e33a64](https://github.com/hirosystems/ordinals-api/commit/0e33a644773990531fa3f68e93ca97f83105a621)) + +## [2.0.0-beta.2](https://github.com/hirosystems/ordinals-api/compare/v2.0.0-beta.1...v2.0.0-beta.2) (2023-11-02) + + +### Bug Fixes + +* reject blocks that would create blessed inscription gaps ([#259](https://github.com/hirosystems/ordinals-api/issues/259)) ([18cd028](https://github.com/hirosystems/ordinals-api/commit/18cd028b636184a96597fa0b0978ba9e4d23f55c)) + +## [2.0.0-beta.1](https://github.com/hirosystems/ordinals-api/compare/v1.2.6...v2.0.0-beta.1) (2023-11-01) + + +### ⚠ BREAKING CHANGES + +* handle transfer types and consider them for BRC-20 indexing (#258) + +### Features + +* handle transfer types and consider them for BRC-20 indexing ([#258](https://github.com/hirosystems/ordinals-api/issues/258)) ([7b83761](https://github.com/hirosystems/ordinals-api/commit/7b83761818ab4a178796a1867d143ff75ef338b0)) + ## [1.2.6](https://github.com/hirosystems/ordinals-api/compare/v1.2.5...v1.2.6) (2023-10-11) diff --git a/migrations/1698856424356_locations-transfer-type.ts b/migrations/1698856424356_locations-transfer-type.ts new file mode 100644 index 00000000..c3ba335f --- /dev/null +++ b/migrations/1698856424356_locations-transfer-type.ts @@ -0,0 +1,14 @@ +/* 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.createType('transfer_type', ['transferred', 'spent_in_fees', 'burnt']); + pgm.addColumn('locations', { + transfer_type: { + type: 'transfer_type', + notNull: true, + }, + }); +} diff --git a/migrations/1698897577725_locations-location-index.ts b/migrations/1698897577725_locations-location-index.ts new file mode 100644 index 00000000..bb7461db --- /dev/null +++ b/migrations/1698897577725_locations-location-index.ts @@ -0,0 +1,14 @@ +/* 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.addColumn('locations', { + block_transfer_index: { + type: 'int', + }, + }); + pgm.addIndex('locations', ['block_height', { name: 'block_transfer_index', sort: 'DESC' }]); + pgm.addIndex('locations', ['block_hash', { name: 'block_transfer_index', sort: 'DESC' }]); +} diff --git a/package-lock.json b/package-lock.json index e7e849ad..479f269c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,8 +14,8 @@ "@fastify/multipart": "^7.1.0", "@fastify/swagger": "^8.3.1", "@fastify/type-provider-typebox": "^3.2.0", - "@hirosystems/api-toolkit": "^1.1.0", - "@hirosystems/chainhook-client": "^1.3.1", + "@hirosystems/api-toolkit": "^1.3.1", + "@hirosystems/chainhook-client": "^1.4.1", "@semantic-release/changelog": "^6.0.3", "@semantic-release/commit-analyzer": "^10.0.4", "@semantic-release/git": "^10.0.1", @@ -55,10 +55,15 @@ }, "../api-toolkit": { "name": "@hirosystems/api-toolkit", - "version": "1.1.0", + "version": "1.3.0", "extraneous": true, "license": "Apache 2.0", "dependencies": { + "@fastify/cors": "^8.0.0", + "@fastify/swagger": "^8.3.1", + "@fastify/type-provider-typebox": "^3.2.0", + "fastify": "^4.3.0", + "fastify-metrics": "^10.2.0", "node-pg-migrate": "^6.2.2", "pino": "^8.11.0", "postgres": "^3.3.4" @@ -85,35 +90,9 @@ "ts-jest": "^29.0.5", "ts-node": "^10.9.1", "typescript": "^5.0.2" - } - }, - "../chainhook/components/client/typescript": { - "name": "@hirosystems/chainhook-client", - "version": "1.3.0", - "extraneous": true, - "license": "Apache 2.0", - "dependencies": { - "@fastify/type-provider-typebox": "^3.2.0", - "fastify": "^4.15.0", - "pino": "^8.11.0", - "undici": "^5.21.2" }, - "devDependencies": { - "@stacks/eslint-config": "^1.2.0", - "@types/jest": "^29.5.0", - "@types/node": "^18.15.7", - "@typescript-eslint/eslint-plugin": "^5.56.0", - "@typescript-eslint/parser": "^5.56.0", - "babel-jest": "^29.5.0", - "eslint": "^8.36.0", - "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-tsdoc": "^0.2.17", - "jest": "^29.5.0", - "prettier": "^2.8.7", - "rimraf": "^4.4.1", - "ts-jest": "^29.0.5", - "ts-node": "^10.9.1", - "typescript": "^5.0.2" + "engines": { + "node": ">=18" } }, "node_modules/@ampproject/remapping": { @@ -1270,22 +1249,30 @@ } }, "node_modules/@hirosystems/api-toolkit": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@hirosystems/api-toolkit/-/api-toolkit-1.1.0.tgz", - "integrity": "sha512-yjlNNRJ4LP+oGrK1ECmZDLutfOwLPu58wbF4eet1Dr9kr36vUGuDEuT6VZKKet7zj7RsUdWW8/SRfUDp+0bppQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@hirosystems/api-toolkit/-/api-toolkit-1.3.1.tgz", + "integrity": "sha512-uUOqWcJlaxnlW30RyZ1UdidzFy29esd4bG0UxwnsJH+M+qtvV4V/NaHLRUFbnJkoF5b6vckZh1mldyBNY7aL1Q==", "dependencies": { + "@fastify/cors": "^8.0.0", + "@fastify/swagger": "^8.3.1", + "@fastify/type-provider-typebox": "^3.2.0", + "fastify": "^4.3.0", + "fastify-metrics": "^10.2.0", "node-pg-migrate": "^6.2.2", "pino": "^8.11.0", "postgres": "^3.3.4" }, "bin": { "api-toolkit-git-info": "bin/api-toolkit-git-info.js" + }, + "engines": { + "node": ">=18" } }, "node_modules/@hirosystems/chainhook-client": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@hirosystems/chainhook-client/-/chainhook-client-1.3.4.tgz", - "integrity": "sha512-oXJxzpzDqbcXIR+vwnCHC62P0AN8AyfrydOO4oOaiqBkt448GB5+Rs9iKUKJt/sVOTBaIlbJvr6uchwYi3PA+w==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@hirosystems/chainhook-client/-/chainhook-client-1.4.1.tgz", + "integrity": "sha512-ZiOk8RlLgtyDc9oRDkWHXGbzhRIMyxRIAi0OHZG/mt5yu3qNzYZ7OmK4txShxAytwDb3okMZEwPK/CrsoLPLXA==", "dependencies": { "@fastify/type-provider-typebox": "^3.2.0", "fastify": "^4.15.0", @@ -19712,19 +19699,24 @@ "requires": {} }, "@hirosystems/api-toolkit": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@hirosystems/api-toolkit/-/api-toolkit-1.1.0.tgz", - "integrity": "sha512-yjlNNRJ4LP+oGrK1ECmZDLutfOwLPu58wbF4eet1Dr9kr36vUGuDEuT6VZKKet7zj7RsUdWW8/SRfUDp+0bppQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@hirosystems/api-toolkit/-/api-toolkit-1.3.1.tgz", + "integrity": "sha512-uUOqWcJlaxnlW30RyZ1UdidzFy29esd4bG0UxwnsJH+M+qtvV4V/NaHLRUFbnJkoF5b6vckZh1mldyBNY7aL1Q==", "requires": { + "@fastify/cors": "^8.0.0", + "@fastify/swagger": "^8.3.1", + "@fastify/type-provider-typebox": "^3.2.0", + "fastify": "^4.3.0", + "fastify-metrics": "^10.2.0", "node-pg-migrate": "^6.2.2", "pino": "^8.11.0", "postgres": "^3.3.4" } }, "@hirosystems/chainhook-client": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@hirosystems/chainhook-client/-/chainhook-client-1.3.4.tgz", - "integrity": "sha512-oXJxzpzDqbcXIR+vwnCHC62P0AN8AyfrydOO4oOaiqBkt448GB5+Rs9iKUKJt/sVOTBaIlbJvr6uchwYi3PA+w==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@hirosystems/chainhook-client/-/chainhook-client-1.4.1.tgz", + "integrity": "sha512-ZiOk8RlLgtyDc9oRDkWHXGbzhRIMyxRIAi0OHZG/mt5yu3qNzYZ7OmK4txShxAytwDb3okMZEwPK/CrsoLPLXA==", "requires": { "@fastify/type-provider-typebox": "^3.2.0", "fastify": "^4.15.0", diff --git a/package.json b/package.json index 46c97509..723f2e3d 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "build": "rimraf ./dist && tsc --project tsconfig.build.json", "start": "node dist/src/index.js", "start-ts": "ts-node ./src/index.ts", - "test": "jest", + "test": "jest --runInBand", "migrate": "ts-node node_modules/.bin/node-pg-migrate -j ts", "lint:eslint": "eslint . --ext .js,.jsx,.ts,.tsx -f unix", "lint:prettier": "prettier --check src/**/*.ts tests/**/*.ts migrations/**/*.ts", @@ -50,8 +50,8 @@ "@fastify/multipart": "^7.1.0", "@fastify/swagger": "^8.3.1", "@fastify/type-provider-typebox": "^3.2.0", - "@hirosystems/api-toolkit": "^1.1.0", - "@hirosystems/chainhook-client": "^1.3.1", + "@hirosystems/api-toolkit": "^1.3.1", + "@hirosystems/chainhook-client": "^1.4.1", "@semantic-release/changelog": "^6.0.3", "@semantic-release/commit-analyzer": "^10.0.4", "@semantic-release/git": "^10.0.1", diff --git a/src/env.ts b/src/env.ts index 72106e43..9f83eed3 100644 --- a/src/env.ts +++ b/src/env.ts @@ -55,11 +55,6 @@ const schema = Type.Object({ /** Enables BRC-20 processing in write mode APIs */ BRC20_BLOCK_SCAN_ENABLED: Type.Boolean({ default: true }), - /** - * Disables inscription genesis/current location calculation, count aggregation, etc. so blocks - * can be ingested faster during a full replay. - */ - FAST_INGESTION_MODE: Type.Boolean({ default: false }), }); type Env = Static; diff --git a/src/pg/brc20/brc20-pg-store.ts b/src/pg/brc20/brc20-pg-store.ts index 60300211..dd0ee33a 100644 --- a/src/pg/brc20/brc20-pg-store.ts +++ b/src/pg/brc20/brc20-pg-store.ts @@ -1,7 +1,13 @@ import { BasePgStoreModule, logger } from '@hirosystems/api-toolkit'; import * as postgres from 'postgres'; import { hexToBuffer } from '../../api/util/helpers'; -import { DbInscription, DbInscriptionIndexPaging, DbLocation, DbPaginatedResult } from '../types'; +import { + DbInscription, + DbInscriptionIndexPaging, + DbLocation, + DbLocationTransferType, + DbPaginatedResult, +} from '../types'; import { BRC20_DEPLOYS_COLUMNS, BRC20_OPERATIONS, @@ -20,18 +26,13 @@ import { DbBrc20TokenWithSupply, DbBrc20TransferEvent, } from './types'; - -import { - Brc20Deploy, - Brc20Mint, - Brc20Transfer, - brc20FromInscriptionContent, - isAddressSentAsFee, -} from './helpers'; - +import { Brc20Deploy, Brc20Mint, Brc20Transfer, brc20FromInscriptionContent } from './helpers'; import { Brc20TokenOrderBy } from '../../api/schemas'; import { objRemoveUndefinedValues } from '../helpers'; +/** The block at which BRC-20 activity began */ +export const BRC20_GENESIS_BLOCK = 779832; + export class Brc20PgStore extends BasePgStoreModule { sqlOr(partials: postgres.PendingQuery[] | undefined) { return partials?.reduce((acc, curr) => this.sql`${acc} OR ${curr}`); @@ -45,6 +46,7 @@ export class Brc20PgStore extends BasePgStoreModule { */ async scanBlocks(startBlock: number, endBlock: number): Promise { for (let blockHeight = startBlock; blockHeight <= endBlock; blockHeight++) { + if (blockHeight < BRC20_GENESIS_BLOCK) continue; logger.info(`Brc20PgStore scanning block ${blockHeight}`); await this.sqlWriteTransaction(async sql => { const limit = 100_000; @@ -53,7 +55,8 @@ export class Brc20PgStore extends BasePgStoreModule { const block = await sql` SELECT EXISTS(SELECT location_id FROM genesis_locations WHERE location_id = l.id) AS genesis, - l.id, l.inscription_id, l.block_height, l.tx_id, l.tx_index, l.address + l.id, l.inscription_id, l.block_height, l.tx_id, l.tx_index, l.address, + l.transfer_type FROM locations AS l INNER JOIN inscriptions AS i ON l.inscription_id = i.id WHERE l.block_height = ${blockHeight} @@ -76,7 +79,7 @@ export class Brc20PgStore extends BasePgStoreModule { if (writes.length === 0) return; for (const write of writes) { if (write.genesis) { - if (isAddressSentAsFee(write.address)) continue; + if (write.transfer_type != DbLocationTransferType.transferred) continue; const content = await this.sql<{ content: string }[]>` SELECT content FROM inscriptions WHERE id = ${write.inscription_id} `; @@ -113,9 +116,15 @@ export class Brc20PgStore extends BasePgStoreModule { if (fromAddressRes.count === 0) return; const fromAddress = fromAddressRes[0].from_address; // Is this transfer sent as fee or from the same sender? If so, we'll return the balance. + // Is it burnt? Mark as empty owner. const returnToSender = - isAddressSentAsFee(location.address) || fromAddress == location.address; - const toAddress = returnToSender ? fromAddress : location.address; + location.transfer_type == DbLocationTransferType.spentInFees || + fromAddress == location.address; + const toAddress = returnToSender + ? fromAddress + : location.transfer_type == DbLocationTransferType.burnt + ? '' + : location.address; // Check if we have a valid transfer inscription emitted by this address that hasn't been sent // to another address before. Use `LIMIT 3` as a quick way of checking if we have just inserted // the first transfer for this inscription (genesis + transfer). @@ -226,7 +235,11 @@ export class Brc20PgStore extends BasePgStoreModule { op: Brc20Deploy; location: DbBrc20Location; }): Promise { - if (!deploy.location.inscription_id || isAddressSentAsFee(deploy.location.address)) return; + if ( + deploy.location.transfer_type != DbLocationTransferType.transferred || + !deploy.location.inscription_id + ) + return; const insert: DbBrc20DeployInsert = { inscription_id: deploy.location.inscription_id, block_height: deploy.location.block_height, @@ -272,7 +285,11 @@ export class Brc20PgStore extends BasePgStoreModule { } private async insertMint(mint: { op: Brc20Mint; location: DbBrc20Location }): Promise { - if (!mint.location.inscription_id || isAddressSentAsFee(mint.location.address)) return; + if ( + mint.location.transfer_type != DbLocationTransferType.transferred || + !mint.location.inscription_id + ) + return; // Check the following conditions: // * Is the mint amount within the allowed token limits? // * Is the number of decimals correct? @@ -349,7 +366,11 @@ export class Brc20PgStore extends BasePgStoreModule { op: Brc20Transfer; location: DbBrc20Location; }): Promise { - if (!transfer.location.inscription_id || isAddressSentAsFee(transfer.location.address)) return; + if ( + transfer.location.transfer_type != DbLocationTransferType.transferred || + !transfer.location.inscription_id + ) + return; // Check the following conditions: // * Do we have enough available balance to do this transfer? const transferRes = await this.sql` diff --git a/src/pg/brc20/helpers.ts b/src/pg/brc20/helpers.ts index a80f0f52..de9d50ab 100644 --- a/src/pg/brc20/helpers.ts +++ b/src/pg/brc20/helpers.ts @@ -79,7 +79,3 @@ export function brc20FromInscriptionContent(content: string): Brc20 | undefined // Not a BRC-20 inscription. } } - -export function isAddressSentAsFee(address: string | null): boolean { - return address === null || address.length === 0; -} diff --git a/src/pg/brc20/types.ts b/src/pg/brc20/types.ts index 03d77e9c..0bb72f33 100644 --- a/src/pg/brc20/types.ts +++ b/src/pg/brc20/types.ts @@ -1,3 +1,5 @@ +import { DbLocationTransferType } from '../types'; + export type DbBrc20Location = { id: string; inscription_id: string | null; @@ -5,6 +7,7 @@ export type DbBrc20Location = { tx_id: string; tx_index: number; address: string | null; + transfer_type: DbLocationTransferType; }; export type DbBrc20ScannedInscription = DbBrc20Location & { diff --git a/src/pg/counts/counts-pg-store.ts b/src/pg/counts/counts-pg-store.ts index d63251fa..8d983719 100644 --- a/src/pg/counts/counts-pg-store.ts +++ b/src/pg/counts/counts-pg-store.ts @@ -56,42 +56,49 @@ export class CountsPgStore extends BasePgStoreModule { async applyInscriptions(writes: DbInscriptionInsert[]): Promise { if (writes.length === 0) return; - const mimeType = new Map(); - const rarity = new Map(); - const recursion = new Map(); - const type = new Map(); + const mimeType = new Map(); + const rarity = new Map(); + const recursion = new Map(); + const typeMap = new Map(); for (const i of writes) { - const t = i.number < 0 ? 'cursed' : 'blessed'; - mimeType.set(i.mime_type, { - mime_type: i.mime_type, - count: mimeType.get(i.mime_type)?.count ?? 0 + 1, - }); - rarity.set(i.sat_rarity, { - sat_rarity: i.sat_rarity, - count: rarity.get(i.sat_rarity)?.count ?? 0 + 1, - }); - recursion.set(i.recursive, { - recursive: i.recursive, - count: recursion.get(i.recursive)?.count ?? 0 + 1, - }); - type.set(t, { type: t, count: type.get(t)?.count ?? 0 + 1 }); + mimeType.set(i.mime_type, (mimeType.get(i.mime_type) ?? 0) + 1); + rarity.set(i.sat_rarity, (rarity.get(i.sat_rarity) ?? 0) + 1); + recursion.set(i.recursive, (recursion.get(i.recursive) ?? 0) + 1); + const inscrType = i.number < 0 ? 'cursed' : 'blessed'; + typeMap.set(inscrType, (typeMap.get(inscrType) ?? 0) + 1); } + const mimeTypeInsert = Array.from(mimeType.entries()).map(k => ({ + mime_type: k[0], + count: k[1], + })); + const rarityInsert = Array.from(rarity.entries()).map(k => ({ + sat_rarity: k[0], + count: k[1], + })); + const recursionInsert = Array.from(recursion.entries()).map(k => ({ + recursive: k[0], + count: k[1], + })); + const typeInsert = Array.from(typeMap.entries()).map(k => ({ + type: k[0], + count: k[1], + })); // `counts_by_address` and `counts_by_genesis_address` count increases are handled in // `applyLocations`. await this.sql` WITH increase_mime_type AS ( - INSERT INTO counts_by_mime_type ${this.sql([...mimeType.values()])} + INSERT INTO counts_by_mime_type ${this.sql(mimeTypeInsert)} ON CONFLICT (mime_type) DO UPDATE SET count = counts_by_mime_type.count + EXCLUDED.count ), increase_rarity AS ( - INSERT INTO counts_by_sat_rarity ${this.sql([...rarity.values()])} + INSERT INTO counts_by_sat_rarity ${this.sql(rarityInsert)} ON CONFLICT (sat_rarity) DO UPDATE SET count = counts_by_sat_rarity.count + EXCLUDED.count ), increase_recursive AS ( - INSERT INTO counts_by_recursive ${this.sql([...recursion.values()])} + INSERT INTO counts_by_recursive ${this.sql(recursionInsert)} ON CONFLICT (recursive) DO UPDATE SET count = counts_by_recursive.count + EXCLUDED.count ) - INSERT INTO counts_by_type ${this.sql([...type.values()])} + INSERT INTO counts_by_type ${this.sql(typeInsert)} ON CONFLICT (type) DO UPDATE SET count = counts_by_type.count + EXCLUDED.count `; } @@ -135,28 +142,28 @@ export class CountsPgStore extends BasePgStoreModule { if (writes.length === 0) return; await this.sqlWriteTransaction(async sql => { const table = genesis ? sql`counts_by_genesis_address` : sql`counts_by_address`; - const oldAddr = new Map(); - const newAddr = new Map(); + const oldAddr = new Map(); + const newAddr = new Map(); for (const i of writes) { - if (i.old_address) - oldAddr.set(i.old_address, { - address: i.old_address, - count: oldAddr.get(i.old_address)?.count ?? 0 + 1, - }); - if (i.new_address) - newAddr.set(i.new_address, { - address: i.new_address, - count: newAddr.get(i.new_address)?.count ?? 0 + 1, - }); + if (i.old_address) oldAddr.set(i.old_address, (oldAddr.get(i.old_address) ?? 0) + 1); + if (i.new_address) newAddr.set(i.new_address, (newAddr.get(i.new_address) ?? 0) + 1); } - if (oldAddr.size) + const oldAddrInsert = Array.from(oldAddr.entries()).map(k => ({ + address: k[0], + count: k[1], + })); + const newAddrInsert = Array.from(newAddr.entries()).map(k => ({ + address: k[0], + count: k[1], + })); + if (oldAddrInsert.length > 0) await sql` - INSERT INTO ${table} ${sql([...oldAddr.values()])} + INSERT INTO ${table} ${sql(oldAddrInsert)} ON CONFLICT (address) DO UPDATE SET count = ${table}.count - EXCLUDED.count `; - if (newAddr.size) + if (newAddrInsert.length > 0) await sql` - INSERT INTO ${table} ${sql([...newAddr.values()])} + INSERT INTO ${table} ${sql(newAddrInsert)} ON CONFLICT (address) DO UPDATE SET count = ${table}.count + EXCLUDED.count `; }); diff --git a/src/pg/helpers.ts b/src/pg/helpers.ts index d9cf0f3c..443b45d2 100644 --- a/src/pg/helpers.ts +++ b/src/pg/helpers.ts @@ -1,5 +1,28 @@ import { PgBytea } from '@hirosystems/api-toolkit'; import { hexToBuffer } from '../api/util/helpers'; +import { BadPayloadRequestError } from '@hirosystems/chainhook-client'; + +/** + * Check if writing a block would create an inscription number gap + * @param currentNumber - Current max blessed number + * @param newNumbers - New blessed numbers to be inserted + */ +export function assertNoBlockInscriptionGap(args: { + currentNumber: number; + newNumbers: number[]; + currentBlockHeight: number; + newBlockHeight: number; +}) { + args.newNumbers.sort((a, b) => a - b); + for (let n = 0; n < args.newNumbers.length; n++) { + const curr = args.currentNumber + n; + const next = args.newNumbers[n]; + if (next !== curr + 1) + throw new BadPayloadRequestError( + `Block inscription gap detected: Attempting to insert #${next} (${args.newBlockHeight}) but current max is #${curr}. Chain tip is at ${args.currentBlockHeight}.` + ); + } +} /** * Returns a list of referenced inscription ids from inscription content. @@ -38,18 +61,11 @@ export function throwOnFirstRejected(settles: { return values; } -/** - * Divides array into equal chunks - * @param arr - Array - * @param chunkSize - Chunk size - * @returns Array of arrays - */ -export function chunkArray(arr: T[], chunkSize: number): T[][] { - const result: T[][] = []; - for (let i = 0; i < arr.length; i += chunkSize) result.push(arr.slice(i, i + chunkSize)); - return result; -} - export function objRemoveUndefinedValues(obj: object) { Object.keys(obj).forEach(key => (obj as any)[key] === undefined && delete (obj as any)[key]); } + +export function removeNullBytes(input: string): string { + // Replace null byte with an empty string + return input.replace(/\x00/g, ''); +} diff --git a/src/pg/pg-store.ts b/src/pg/pg-store.ts index 288f8352..25fdbc52 100644 --- a/src/pg/pg-store.ts +++ b/src/pg/pg-store.ts @@ -2,8 +2,9 @@ import { BasePgStore, PgConnectionVars, PgSqlClient, + PgSqlQuery, + batchIterate, connectPostgres, - isTestEnv, logger, runMigrations, } from '@hirosystems/api-toolkit'; @@ -17,7 +18,7 @@ import { ENV } from '../env'; import { Brc20PgStore } from './brc20/brc20-pg-store'; import { CountsPgStore } from './counts/counts-pg-store'; import { getIndexResultCountType } from './counts/helpers'; -import { chunkArray, getInscriptionRecursion } from './helpers'; +import { assertNoBlockInscriptionGap, getInscriptionRecursion, removeNullBytes } from './helpers'; import { DbFullyLocatedInscriptionResult, DbInscription, @@ -32,16 +33,18 @@ import { DbLocation, DbLocationPointer, DbLocationPointerInsert, + DbLocationTransferType, DbPaginatedResult, DbRevealInsert, INSCRIPTIONS_COLUMNS, LOCATIONS_COLUMNS, } from './types'; +import { toEnumValue } from '@hirosystems/api-toolkit'; export const MIGRATIONS_DIR = path.join(__dirname, '../../migrations'); +export const ORDINALS_GENESIS_BLOCK = 767430; type InscriptionIdentifier = { genesis_id: string } | { number: number }; -type PgQueryFragment = postgres.PendingQuery; // TODO: Move to api-toolkit export class PgStore extends BasePgStore { readonly brc20: Brc20PgStore; @@ -85,6 +88,13 @@ export class PgStore extends BasePgStore { async updateInscriptions(payload: Payload): Promise { let updatedBlockHeightMin = Infinity; await this.sqlWriteTransaction(async sql => { + // Check where we're at in terms of ingestion, e.g. block height and max blessed inscription + // number. This will let us determine if we should skip ingesting this block or throw an error + // if a gap is detected. + const currentBlessedNumber = (await this.getMaxInscriptionNumber()) ?? -1; + const currentBlockHeight = await this.getChainTipBlockHeight(); + const newBlessedNumbers: number[] = []; + for (const rollbackEvent of payload.rollback) { // TODO: Optimize rollbacks just as we optimized applys. const event = rollbackEvent as BitcoinEvent; @@ -113,9 +123,17 @@ export class PgStore extends BasePgStore { } updatedBlockHeightMin = Math.min(updatedBlockHeightMin, event.block_identifier.index); } + + let blockTransferIndex = 0; for (const applyEvent of payload.apply) { const event = applyEvent as BitcoinEvent; const block_height = event.block_identifier.index; + if (block_height <= currentBlockHeight && block_height !== ORDINALS_GENESIS_BLOCK) { + logger.info( + `PgStore skipping ingestion for previously seen block ${block_height}, current chain tip is at ${currentBlockHeight}` + ); + return; + } const block_hash = normalizedHexString(event.block_identifier.hash); const writes: DbRevealInsert[] = []; for (const tx of event.transactions) { @@ -123,17 +141,20 @@ export class PgStore extends BasePgStore { for (const operation of tx.metadata.ordinal_operations) { if (operation.inscription_revealed) { const reveal = operation.inscription_revealed; + if (reveal.inscription_number >= 0) + newBlessedNumbers.push(parseInt(`${reveal.inscription_number}`)); const satoshi = new OrdinalSatoshi(reveal.ordinal_number); const satpoint = parseSatPoint(reveal.satpoint_post_inscription); const recursive_refs = getInscriptionRecursion(reveal.content_bytes); + const contentType = removeNullBytes(reveal.content_type); writes.push({ inscription: { genesis_id: reveal.inscription_id, - mime_type: reveal.content_type.split(';')[0], - content_type: reveal.content_type, + mime_type: contentType.split(';')[0], + content_type: contentType, content_length: reveal.content_length, number: reveal.inscription_number, - content: reveal.content_bytes, + content: removeNullBytes(reveal.content_bytes), fee: reveal.inscription_fee.toString(), curse_type: null, sat_ordinal: reveal.ordinal_number.toString(), @@ -146,6 +167,7 @@ export class PgStore extends BasePgStore { block_height, tx_id, tx_index: reveal.tx_index, + block_transfer_index: null, genesis_id: reveal.inscription_id, address: reveal.inscriber_address, output: `${satpoint.tx_id}:${satpoint.vout}`, @@ -154,6 +176,7 @@ export class PgStore extends BasePgStore { prev_offset: null, value: reveal.inscription_output_value.toString(), timestamp: event.timestamp, + transfer_type: DbLocationTransferType.transferred, }, recursive_refs, }); @@ -163,14 +186,15 @@ export class PgStore extends BasePgStore { const satoshi = new OrdinalSatoshi(reveal.ordinal_number); const satpoint = parseSatPoint(reveal.satpoint_post_inscription); const recursive_refs = getInscriptionRecursion(reveal.content_bytes); + const contentType = removeNullBytes(reveal.content_type); writes.push({ inscription: { genesis_id: reveal.inscription_id, - mime_type: reveal.content_type.split(';')[0], - content_type: reveal.content_type, + mime_type: contentType.split(';')[0], + content_type: contentType, content_length: reveal.content_length, number: reveal.inscription_number, - content: reveal.content_bytes, + content: removeNullBytes(reveal.content_bytes), fee: reveal.inscription_fee.toString(), curse_type: JSON.stringify(reveal.curse_type), sat_ordinal: reveal.ordinal_number.toString(), @@ -183,6 +207,7 @@ export class PgStore extends BasePgStore { block_height, tx_id, tx_index: reveal.tx_index, + block_transfer_index: null, genesis_id: reveal.inscription_id, address: reveal.inscriber_address, output: `${satpoint.tx_id}:${satpoint.vout}`, @@ -191,6 +216,7 @@ export class PgStore extends BasePgStore { prev_offset: null, value: reveal.inscription_output_value.toString(), timestamp: event.timestamp, + transfer_type: DbLocationTransferType.transferred, }, recursive_refs, }); @@ -205,8 +231,9 @@ export class PgStore extends BasePgStore { block_height, tx_id, tx_index: transfer.tx_index, + block_transfer_index: blockTransferIndex++, genesis_id: transfer.inscription_id, - address: transfer.updated_address, + address: transfer.destination.value ?? null, output: `${satpoint.tx_id}:${satpoint.vout}`, offset: satpoint.offset ?? null, prev_output: `${prevSatpoint.tx_id}:${prevSatpoint.vout}`, @@ -215,14 +242,23 @@ export class PgStore extends BasePgStore { ? transfer.post_transfer_output_value.toString() : null, timestamp: event.timestamp, + transfer_type: + toEnumValue(DbLocationTransferType, transfer.destination.type) ?? + DbLocationTransferType.transferred, }, }); } } } - // Divide insertion array into chunks of 5000 in order to avoid the postgres limit of 65534 + assertNoBlockInscriptionGap({ + currentNumber: currentBlessedNumber, + newNumbers: newBlessedNumbers, + currentBlockHeight: currentBlockHeight, + newBlockHeight: block_height, + }); + // Divide insertion array into chunks of 4000 in order to avoid the postgres limit of 65534 // query params. - for (const writeChunk of chunkArray(writes, 5000)) + for (const writeChunk of batchIterate(writes, 4000)) await this.insertInscriptions(writeChunk); updatedBlockHeightMin = Math.min(updatedBlockHeightMin, event.block_identifier.index); if (ENV.BRC20_BLOCK_SCAN_ENABLED) @@ -230,15 +266,8 @@ export class PgStore extends BasePgStore { } }); await this.refreshMaterializedView('chain_tip'); - // Skip expensive view refreshes if we're not streaming live blocks. - if (payload.chainhook.is_streaming_blocks) { - // We'll issue materialized view refreshes in parallel. We will not wait for them to finish so - // we can respond to the chainhook node with a `200` HTTP code as soon as possible. - const views = [this.normalizeInscriptionCount({ min_block_height: updatedBlockHeightMin })]; - const viewRefresh = Promise.allSettled(views); - // Only wait for these on tests. - if (isTestEnv) await viewRefresh; - } + if (updatedBlockHeightMin !== Infinity) + await this.normalizeInscriptionCount({ min_block_height: updatedBlockHeightMin }); } async getChainTipBlockHeight(): Promise { @@ -396,8 +425,10 @@ export class PgStore extends BasePgStore { } ${filters?.to_sat_ordinal ? sql`AND i.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?.from_number !== undefined ? sql`AND i.number >= ${filters.from_number}` : sql`` + } + ${filters?.to_number !== undefined ? sql`AND i.number <= ${filters.to_number}` : sql``} ${filters?.address?.length ? sql`AND cur.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 cur_l.output = ${filters.output}` : sql``} @@ -495,7 +526,14 @@ export class PgStore extends BasePgStore { args: { block_height?: number; block_hash?: string } & DbInscriptionIndexPaging ): Promise> { const results = await this.sql<({ total: number } & DbInscriptionLocationChange)[]>` - WITH transfers AS ( + WITH max_transfer_index AS ( + SELECT MAX(block_transfer_index) FROM locations WHERE ${ + 'block_height' in args + ? this.sql`block_height = ${args.block_height}` + : this.sql`block_hash = ${args.block_hash}` + } AND block_transfer_index IS NOT NULL + ), + transfers AS ( SELECT i.id AS inscription_id, i.genesis_id, @@ -512,31 +550,30 @@ export class PgStore extends BasePgStore { ) ORDER BY ll.block_height DESC LIMIT 1 - ) AS from_id, - COUNT(*) OVER() as total + ) AS from_id FROM locations AS l INNER JOIN inscriptions AS i ON l.inscription_id = i.id WHERE - NOT EXISTS (SELECT location_id FROM genesis_locations WHERE location_id = l.id) - AND ${ 'block_height' in args ? this.sql`l.block_height = ${args.block_height}` : this.sql`l.block_hash = ${args.block_hash}` } - LIMIT ${args.limit} - OFFSET ${args.offset} + AND l.block_transfer_index IS NOT NULL + AND l.block_transfer_index <= ((SELECT max FROM max_transfer_index) - ${args.offset}::int) + AND l.block_transfer_index > + ((SELECT max FROM max_transfer_index) - (${args.offset}::int + ${args.limit}::int)) ) SELECT t.genesis_id, t.number, - t.total, + (SELECT max FROM max_transfer_index) + 1 AS total, ${this.sql.unsafe(LOCATIONS_COLUMNS.map(c => `lf.${c} AS from_${c}`).join(','))}, ${this.sql.unsafe(LOCATIONS_COLUMNS.map(c => `lt.${c} AS to_${c}`).join(','))} FROM transfers AS t INNER JOIN locations AS lf ON t.from_id = lf.id INNER JOIN locations AS lt ON t.to_id = lt.id - ORDER BY to_tx_index DESC + ORDER BY lt.block_transfer_index DESC `; return { total: results[0]?.total ?? 0, @@ -634,17 +671,14 @@ export class PgStore extends BasePgStore { RETURNING inscription_id, id AS location_id, block_height, tx_index, address `; await this.updateInscriptionRecursions(writes); - if (!ENV.FAST_INGESTION_MODE) { - if (transferGenesisIds.size) - await sql` - UPDATE inscriptions - SET updated_at = NOW() - WHERE genesis_id IN ${sql([...transferGenesisIds])} - `; - await this.backfillOrphanLocations(); - await this.updateInscriptionLocationPointers(locations); - await this.counts.applyInscriptions(inscriptions); - } + if (transferGenesisIds.size) + await sql` + UPDATE inscriptions + SET updated_at = NOW() + WHERE genesis_id IN ${sql([...transferGenesisIds])} + `; + await this.updateInscriptionLocationPointers(locations); + await this.counts.applyInscriptions(inscriptions); for (const reveal of writes) { const action = reveal.inscription ? `reveal #${reveal.inscription.number}` : `transfer`; logger.info( @@ -818,21 +852,6 @@ export class PgStore extends BasePgStore { }); } - private async backfillOrphanLocations(): Promise { - await this.sqlWriteTransaction(async sql => { - await sql` - UPDATE locations AS l - SET inscription_id = (SELECT id FROM inscriptions WHERE genesis_id = l.genesis_id) - WHERE l.inscription_id IS NULL - `; - await sql` - UPDATE inscription_recursions AS l - SET ref_inscription_id = (SELECT id FROM inscriptions WHERE genesis_id = l.ref_inscription_genesis_id) - WHERE l.ref_inscription_id IS NULL - `; - }); - } - private async recalculateCurrentLocationPointerFromLocationRollBack(args: { location: DbLocation; }): Promise { @@ -867,8 +886,8 @@ export class PgStore extends BasePgStore { private async updateInscriptionRecursions(reveals: DbRevealInsert[]): Promise { if (reveals.length === 0) return; const inserts: { - inscription_id: PgQueryFragment; - ref_inscription_id: PgQueryFragment; + inscription_id: PgSqlQuery; + ref_inscription_id: PgSqlQuery; ref_inscription_genesis_id: string; }[] = []; for (const i of reveals) @@ -884,7 +903,7 @@ export class PgStore extends BasePgStore { } if (inserts.length === 0) return; await this.sqlWriteTransaction(async sql => { - for (const chunk of chunkArray(inserts, 500)) + for (const chunk of batchIterate(inserts, 500)) await sql` INSERT INTO inscription_recursions ${sql(chunk)} ON CONFLICT ON CONSTRAINT inscription_recursions_unique DO NOTHING diff --git a/src/pg/types.ts b/src/pg/types.ts index 73c3e693..c0e11ff4 100644 --- a/src/pg/types.ts +++ b/src/pg/types.ts @@ -34,6 +34,12 @@ export type DbFullyLocatedInscriptionResult = { recursion_refs: string | null; }; +export enum DbLocationTransferType { + transferred = 'transferred', + spentInFees = 'spent_in_fees', + burnt = 'burnt', +} + export type DbLocationInsert = { genesis_id: string; block_height: number; @@ -47,6 +53,8 @@ export type DbLocationInsert = { prev_offset: PgNumeric | null; value: PgNumeric | null; timestamp: number; + transfer_type: DbLocationTransferType; + block_transfer_index: number | null; }; export type DbLocation = { diff --git a/tests/brc20.test.ts b/tests/brc20.test.ts index 042fa51f..8f51852d 100644 --- a/tests/brc20.test.ts +++ b/tests/brc20.test.ts @@ -1,4 +1,4 @@ -import { cycleMigrations } from '@hirosystems/api-toolkit'; +import { runMigrations } from '@hirosystems/api-toolkit'; import { buildApiServer } from '../src/api/init'; import { Brc20ActivityResponse, Brc20TokenResponse } from '../src/api/schemas'; import { brc20FromInscription } from '../src/pg/brc20/helpers'; @@ -12,6 +12,7 @@ import { randomHash, rollBack, } from './helpers'; +import { BRC20_GENESIS_BLOCK } from '../src/pg/brc20/brc20-pg-store'; describe('BRC-20', () => { let db: PgStore; @@ -22,7 +23,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: BRC20_GENESIS_BLOCK, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', }) .transaction({ @@ -36,7 +37,7 @@ describe('BRC-20', () => { tick: 'PEPE', max: '250000', }, - number: 5, + number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: address, }) @@ -47,7 +48,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775618, + height: BRC20_GENESIS_BLOCK + 1, hash: '0000000000000000000098d8f2663891d439f6bb7de230d4e9f6bcc2e85452bf', }) .transaction({ @@ -61,7 +62,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '10000', }, - number: 6, + number: 1, tx_id: '3b55f624eaa4f8de6c42e0c490176b67123a83094384f658611faf7bfb85dd0f', address: address, }) @@ -71,14 +72,15 @@ describe('BRC-20', () => { }; beforeEach(async () => { + await runMigrations(MIGRATIONS_DIR, 'up'); db = await PgStore.connect({ skipMigrations: true }); fastify = await buildApiServer({ db }); - await cycleMigrations(MIGRATIONS_DIR); }); afterEach(async () => { await fastify.close(); await db.close(); + await runMigrations(MIGRATIONS_DIR, 'down'); }); describe('token standard validation', () => { @@ -86,7 +88,7 @@ describe('BRC-20', () => { const content = Buffer.from(JSON.stringify(json), 'utf-8'); const insert: DbInscriptionInsert = { genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - number: 1, + number: 0, mime_type: 'application/json', content_type: 'application/json', content_length: content.length, @@ -113,7 +115,7 @@ describe('BRC-20', () => { ); const insert: DbInscriptionInsert = { genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - number: 1, + number: 0, mime_type: 'foo/bar', content_type: 'foo/bar;x=1', content_length: content.length, @@ -141,7 +143,7 @@ describe('BRC-20', () => { ); const insert: DbInscriptionInsert = { genesis_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - number: 1, + number: 0, mime_type: 'application/json', content_type: 'application/json', content_length: content.length, @@ -472,7 +474,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: BRC20_GENESIS_BLOCK, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1677811111, }) @@ -487,7 +489,7 @@ describe('BRC-20', () => { tick: 'PEPE', max: '21000000', }, - number: 5, + number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', }) @@ -504,10 +506,10 @@ describe('BRC-20', () => { expect(responseJson1.results).toStrictEqual([ { address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - block_height: 775617, + block_height: BRC20_GENESIS_BLOCK, decimals: 18, id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - number: 5, + number: 0, mint_limit: null, max_supply: '21000000.000000000000000000', ticker: 'PEPE', @@ -524,7 +526,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: BRC20_GENESIS_BLOCK, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', }) .transaction({ @@ -538,7 +540,7 @@ describe('BRC-20', () => { tick: 'PEPE', max: '21000000', }, - number: 5, + number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', }) @@ -549,7 +551,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775618, + height: BRC20_GENESIS_BLOCK + 1, hash: '000000000000000000021a0207fa97024506baaa74396822fb0a07ac20e70148', }) .transaction({ @@ -563,7 +565,7 @@ describe('BRC-20', () => { tick: 'PEPE', max: '19000000', }, - number: 6, + number: 1, tx_id: '3f8067a6e9b45308b5a090c2987feeb2d08cbaf814ef2ffabad7c381b62f5f7e', address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', }) @@ -580,12 +582,12 @@ describe('BRC-20', () => { expect(responseJson1.results).toStrictEqual([ { address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - block_height: 775617, + block_height: BRC20_GENESIS_BLOCK, decimals: 18, id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', max_supply: '21000000.000000000000000000', mint_limit: null, - number: 5, + number: 0, ticker: 'PEPE', tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', deploy_timestamp: 1677803510000, @@ -600,7 +602,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: BRC20_GENESIS_BLOCK, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', }) .transaction({ @@ -614,7 +616,7 @@ describe('BRC-20', () => { tick: 'PEPE', max: '21000000', }, - number: 5, + number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', }) @@ -625,7 +627,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775618, + height: BRC20_GENESIS_BLOCK + 1, hash: '000000000000000000021a0207fa97024506baaa74396822fb0a07ac20e70148', }) .transaction({ @@ -639,7 +641,7 @@ describe('BRC-20', () => { tick: 'pepe', max: '19000000', }, - number: 6, + number: 1, tx_id: '3f8067a6e9b45308b5a090c2987feeb2d08cbaf814ef2ffabad7c381b62f5f7e', address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', }) @@ -656,12 +658,12 @@ describe('BRC-20', () => { expect(responseJson1.results).toStrictEqual([ { address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - block_height: 775617, + block_height: BRC20_GENESIS_BLOCK, decimals: 18, id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', max_supply: '21000000.000000000000000000', mint_limit: null, - number: 5, + number: 0, ticker: 'PEPE', tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', deploy_timestamp: 1677803510000, @@ -679,12 +681,12 @@ describe('BRC-20', () => { expect(responseJson2.results).toStrictEqual([ { address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - block_height: 775617, + block_height: BRC20_GENESIS_BLOCK, decimals: 18, id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', max_supply: '21000000.000000000000000000', mint_limit: null, - number: 5, + number: 0, ticker: 'PEPE', tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', deploy_timestamp: 1677803510000, @@ -702,7 +704,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: BRC20_GENESIS_BLOCK, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', }) .transaction({ @@ -716,7 +718,7 @@ describe('BRC-20', () => { tick: 'PEPE', max: '21000000', }, - number: 5, + number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: address, }) @@ -727,7 +729,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775618, + height: BRC20_GENESIS_BLOCK + 1, hash: '0000000000000000000098d8f2663891d439f6bb7de230d4e9f6bcc2e85452bf', }) .transaction({ @@ -741,7 +743,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '250000', }, - number: 6, + number: 1, tx_id: '8aec77f855549d98cb9fb5f35e02a03f9a2354fd05a5f89fc610b32c3b01f99f', address: address, }) @@ -770,7 +772,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775619, + height: BRC20_GENESIS_BLOCK + 2, hash: '0000000000000000000077163227125e51d838787d6af031bc9b55a3a1cc1b2c', }) .transaction({ @@ -784,7 +786,7 @@ describe('BRC-20', () => { tick: 'pepe', amt: '100000', }, - number: 7, + number: 2, tx_id: '7a1adbc3e93ddf8d7c4e0ba75aa11c98c431521dd850be8b955feedb716d8bec', address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', }) @@ -828,7 +830,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: BRC20_GENESIS_BLOCK, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', }) .transaction({ @@ -842,7 +844,7 @@ describe('BRC-20', () => { tick: 'PEPE', max: '21000000', }, - number: 5, + number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: address, }) @@ -853,7 +855,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775618, + height: BRC20_GENESIS_BLOCK + 1, hash: '0000000000000000000098d8f2663891d439f6bb7de230d4e9f6bcc2e85452bf', }) .transaction({ @@ -867,7 +869,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '250000', }, - number: 6, + number: 1, tx_id: '8aec77f855549d98cb9fb5f35e02a03f9a2354fd05a5f89fc610b32c3b01f99f', address: address, }) @@ -879,7 +881,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .rollback() .block({ - height: 775619, + height: BRC20_GENESIS_BLOCK + 2, hash: '0000000000000000000077163227125e51d838787d6af031bc9b55a3a1cc1b2c', }) .transaction({ @@ -893,7 +895,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '250000', }, - number: 6, + number: 1, tx_id: '8aec77f855549d98cb9fb5f35e02a03f9a2354fd05a5f89fc610b32c3b01f99f', address: address, }) @@ -923,7 +925,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: BRC20_GENESIS_BLOCK, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', }) .transaction({ @@ -938,7 +940,7 @@ describe('BRC-20', () => { max: '21000000', dec: '1', }, - number: 5, + number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: address, }) @@ -949,7 +951,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775618, + height: BRC20_GENESIS_BLOCK + 1, hash: '0000000000000000000098d8f2663891d439f6bb7de230d4e9f6bcc2e85452bf', }) .transaction({ @@ -963,7 +965,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '250000.000', // Invalid decimal count }, - number: 6, + number: 1, tx_id: '8aec77f855549d98cb9fb5f35e02a03f9a2354fd05a5f89fc610b32c3b01f99f', address: address, }) @@ -987,7 +989,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: BRC20_GENESIS_BLOCK, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', }) .transaction({ @@ -1002,7 +1004,7 @@ describe('BRC-20', () => { max: '2500', dec: '1', }, - number: 5, + number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: address, }) @@ -1013,7 +1015,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775618, + height: BRC20_GENESIS_BLOCK + 1, hash: '0000000000000000000098d8f2663891d439f6bb7de230d4e9f6bcc2e85452bf', }) .transaction({ @@ -1027,7 +1029,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '1000', }, - number: 6, + number: 1, tx_id: '3b55f624eaa4f8de6c42e0c490176b67123a83094384f658611faf7bfb85dd0f', address: address, }) @@ -1043,7 +1045,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '1000', }, - number: 7, + number: 2, tx_id: '7e09bda2cba34bca648cca6d79a074940d39b6137150d3a3edcf80c0e01419a5', address: address, }) @@ -1059,7 +1061,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '5000000000', // Exceeds supply }, - number: 8, + number: 3, tx_id: '8aec77f855549d98cb9fb5f35e02a03f9a2354fd05a5f89fc610b32c3b01f99f', address: address, }) @@ -1088,7 +1090,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775619, + height: BRC20_GENESIS_BLOCK + 2, hash: '000000000000000000001f14513d722146fddab04a1855665a5eca22df288c3c', }) .transaction({ @@ -1102,7 +1104,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '1000', }, - number: 9, + number: 4, tx_id: 'bf7a3e1a0647ca88f6539119b2defaec302683704ea270b3302e709597643548', address: address, }) @@ -1125,7 +1127,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775618, + height: BRC20_GENESIS_BLOCK + 1, hash: '0000000000000000000098d8f2663891d439f6bb7de230d4e9f6bcc2e85452bf', }) .transaction({ @@ -1139,7 +1141,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '1000', }, - number: 6, + number: 0, tx_id: '3b55f624eaa4f8de6c42e0c490176b67123a83094384f658611faf7bfb85dd0f', address: address, }) @@ -1163,7 +1165,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: BRC20_GENESIS_BLOCK, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', }) .transaction({ @@ -1179,7 +1181,7 @@ describe('BRC-20', () => { dec: '1', lim: '100', }, - number: 5, + number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: address, }) @@ -1190,7 +1192,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775618, + height: BRC20_GENESIS_BLOCK + 1, hash: '0000000000000000000098d8f2663891d439f6bb7de230d4e9f6bcc2e85452bf', }) .transaction({ @@ -1204,7 +1206,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '1000', // Greater than limit }, - number: 6, + number: 1, tx_id: '3b55f624eaa4f8de6c42e0c490176b67123a83094384f658611faf7bfb85dd0f', address: address, }) @@ -1231,7 +1233,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775619, + height: BRC20_GENESIS_BLOCK + 2, hash: '00000000000000000002b14f0c5dde0b2fc74d022e860696bd64f1f652756674', }) .transaction({ @@ -1245,7 +1247,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '2000', }, - number: 7, + number: 2, tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', address: address, }) @@ -1272,7 +1274,7 @@ describe('BRC-20', () => { // Balance at previous block const response2 = await fastify.inject({ method: 'GET', - url: `/ordinals/brc-20/balances/${address}?block_height=775618`, + url: `/ordinals/brc-20/balances/${address}?block_height=779833`, }); const json2 = response2.json(); expect(json2.results[0].available_balance).toBe('10000.000000000000000000'); @@ -1285,7 +1287,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775619, + height: BRC20_GENESIS_BLOCK + 2, hash: '00000000000000000002b14f0c5dde0b2fc74d022e860696bd64f1f652756674', }) .transaction({ @@ -1299,7 +1301,7 @@ describe('BRC-20', () => { tick: 'TEST', // Not found amt: '2000', }, - number: 7, + number: 2, tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', address: address, }) @@ -1331,7 +1333,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775619, + height: BRC20_GENESIS_BLOCK + 2, hash: '00000000000000000002b14f0c5dde0b2fc74d022e860696bd64f1f652756674', }) .transaction({ @@ -1345,7 +1347,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '5000000000', // More than was minted }, - number: 7, + number: 2, tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', address: address, }) @@ -1377,7 +1379,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775619, + height: BRC20_GENESIS_BLOCK + 2, hash: '00000000000000000002b14f0c5dde0b2fc74d022e860696bd64f1f652756674', }) .transaction({ @@ -1391,7 +1393,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '9000', }, - number: 7, + number: 2, tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', address: address, }) @@ -1407,7 +1409,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '2000', // Will exceed available balance }, - number: 8, + number: 3, tx_id: '7edaa48337a94da327b6262830505f116775a32db5ad4ad46e87ecea33f21bac', address: address, }) @@ -1440,7 +1442,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775619, + height: BRC20_GENESIS_BLOCK + 2, hash: '00000000000000000002b14f0c5dde0b2fc74d022e860696bd64f1f652756674', }) .transaction({ @@ -1454,7 +1456,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '9000', }, - number: 7, + number: 2, tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', address: address, }) @@ -1465,7 +1467,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775620, + height: BRC20_GENESIS_BLOCK + 3, hash: '00000000000000000003feae13d107f0f2c4fb4dd08fb2a8b1ab553512e77f03', }) .transaction({ @@ -1473,7 +1475,7 @@ describe('BRC-20', () => { }) .inscriptionTransferred({ inscription_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47ai0', - updated_address: address2, + destination: { type: 'transferred', value: address2 }, satpoint_pre_transfer: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a:0:0', satpoint_post_transfer: @@ -1519,13 +1521,13 @@ describe('BRC-20', () => { // Balance at previous block const prevBlock1 = await fastify.inject({ method: 'GET', - url: `/ordinals/brc-20/balances/${address}?block_height=775618`, + url: `/ordinals/brc-20/balances/${address}?block_height=779833`, }); const prevBlockJson1 = prevBlock1.json(); expect(prevBlockJson1.results[0].available_balance).toBe('10000.000000000000000000'); const prevBlock2 = await fastify.inject({ method: 'GET', - url: `/ordinals/brc-20/balances/${address2}?block_height=775618`, + url: `/ordinals/brc-20/balances/${address2}?block_height=779833`, }); const prevBlockJson2 = prevBlock2.json(); expect(prevBlockJson2.results[0]).toBeUndefined(); @@ -1538,7 +1540,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775619, + height: BRC20_GENESIS_BLOCK + 2, hash: '00000000000000000002b14f0c5dde0b2fc74d022e860696bd64f1f652756674', }) .transaction({ @@ -1552,7 +1554,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '9000', }, - number: 7, + number: 2, tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', address: address, }) @@ -1563,7 +1565,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775620, + height: BRC20_GENESIS_BLOCK + 3, hash: '00000000000000000003feae13d107f0f2c4fb4dd08fb2a8b1ab553512e77f03', }) .transaction({ @@ -1571,7 +1573,7 @@ describe('BRC-20', () => { }) .inscriptionTransferred({ inscription_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47ai0', - updated_address: '', // Sent as fee + destination: { type: 'spent_in_fees', value: '' }, satpoint_pre_transfer: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a:0:0', satpoint_post_transfer: @@ -1599,6 +1601,74 @@ describe('BRC-20', () => { ]); }); + test('sending transfer to unspendable output does not return to sender', async () => { + const address = 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td'; + await deployAndMintPEPE(address); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: BRC20_GENESIS_BLOCK + 2, + hash: '00000000000000000002b14f0c5dde0b2fc74d022e860696bd64f1f652756674', + }) + .transaction({ + hash: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', + }) + .inscriptionRevealed( + brc20Reveal({ + json: { + p: 'brc-20', + op: 'transfer', + tick: 'PEPE', + amt: '9000', + }, + number: 2, + tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', + address: address, + }) + ) + .build() + ); + await db.updateInscriptions( + new TestChainhookPayloadBuilder() + .apply() + .block({ + height: BRC20_GENESIS_BLOCK + 3, + hash: '00000000000000000003feae13d107f0f2c4fb4dd08fb2a8b1ab553512e77f03', + }) + .transaction({ + hash: '7edaa48337a94da327b6262830505f116775a32db5ad4ad46e87ecea33f21bac', + }) + .inscriptionTransferred({ + inscription_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47ai0', + destination: { type: 'burnt' }, + satpoint_pre_transfer: + 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a:0:0', + satpoint_post_transfer: + '7edaa48337a94da327b6262830505f116775a32db5ad4ad46e87ecea33f21bac:0:0', + post_transfer_output_value: null, + tx_index: 0, + }) + .build() + ); + + const response1 = await fastify.inject({ + method: 'GET', + url: `/ordinals/brc-20/balances/${address}`, + }); + expect(response1.statusCode).toBe(200); + const json1 = response1.json(); + expect(json1.total).toBe(1); + expect(json1.results).toStrictEqual([ + { + available_balance: '1000.000000000000000000', + overall_balance: '1000.000000000000000000', + ticker: 'PEPE', + transferrable_balance: '0.000000000000000000', + }, + ]); + }); + test('cannot spend valid transfer twice', async () => { const address = 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td'; const address2 = '3QNjwPDRafjBm9XxJpshgk3ksMJh3TFxTU'; @@ -1607,7 +1677,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775619, + height: BRC20_GENESIS_BLOCK + 2, hash: '00000000000000000002b14f0c5dde0b2fc74d022e860696bd64f1f652756674', }) .transaction({ @@ -1621,7 +1691,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '9000', }, - number: 7, + number: 2, tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', address: address, }) @@ -1632,7 +1702,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775620, + height: BRC20_GENESIS_BLOCK + 3, hash: '000000000000000000016ddf56d0fe72476165acee9500d48d3e2aaf8412f489', }) .transaction({ @@ -1640,7 +1710,7 @@ describe('BRC-20', () => { }) .inscriptionTransferred({ inscription_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47ai0', - updated_address: address2, + destination: { type: 'transferred', value: address2 }, satpoint_pre_transfer: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a:0:0', satpoint_post_transfer: @@ -1656,7 +1726,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775621, + height: BRC20_GENESIS_BLOCK + 4, hash: '00000000000000000003feae13d107f0f2c4fb4dd08fb2a8b1ab553512e77f03', }) .transaction({ @@ -1664,7 +1734,7 @@ describe('BRC-20', () => { }) .inscriptionTransferred({ inscription_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47ai0', - updated_address: address, + destination: { type: 'transferred', value: address }, satpoint_pre_transfer: '7edaa48337a94da327b6262830505f116775a32db5ad4ad46e87ecea33f21bac:0:0', satpoint_post_transfer: @@ -1731,7 +1801,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '20', }, - number: 10, + number: 2, tx_id: '825a25b64b5d99ca30e04e53cc9a3020412e1054eb2a7523eb075ddd6d983205', address: address, }) @@ -1750,7 +1820,7 @@ describe('BRC-20', () => { }) .inscriptionTransferred({ inscription_id: '825a25b64b5d99ca30e04e53cc9a3020412e1054eb2a7523eb075ddd6d983205i0', - updated_address: address2, + destination: { type: 'transferred', value: address2 }, satpoint_pre_transfer: '825a25b64b5d99ca30e04e53cc9a3020412e1054eb2a7523eb075ddd6d983205:0:0', satpoint_post_transfer: @@ -1790,7 +1860,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '20', }, - number: 11, + number: 3, tx_id: '09a812f72275892b4858880cf3821004a6e8885817159b340639afe9952ac053', address: address2, }) @@ -1821,7 +1891,7 @@ describe('BRC-20', () => { }) .inscriptionTransferred({ inscription_id: '09a812f72275892b4858880cf3821004a6e8885817159b340639afe9952ac053i0', - updated_address: address2, + destination: { type: 'transferred', value: address2 }, satpoint_pre_transfer: '486815e61723d03af344e1256d7e0c028a8e9e71eb38157f4bf069eb94292ee1:0:0', satpoint_post_transfer: @@ -1852,7 +1922,7 @@ describe('BRC-20', () => { await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() - .block({ height: 775617 }) + .block({ height: BRC20_GENESIS_BLOCK }) .transaction({ hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', }) @@ -1864,7 +1934,7 @@ describe('BRC-20', () => { tick: 'PEPE', max: '21000000', }, - number: 5, + number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', }) @@ -1879,8 +1949,8 @@ describe('BRC-20', () => { expect(response.json()).toStrictEqual({ token: { id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - number: 5, - block_height: 775617, + number: 0, + block_height: BRC20_GENESIS_BLOCK, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', ticker: 'PEPE', @@ -1900,8 +1970,8 @@ describe('BRC-20', () => { }); test('tokens filter by ticker prefix', async () => { - const inscriptionNumbers = incrementing(1); - const blockHeights = incrementing(775600); + const inscriptionNumbers = incrementing(0); + const blockHeights = incrementing(BRC20_GENESIS_BLOCK); let transferHash = randomHash(); await db.updateInscriptions( @@ -2008,8 +2078,8 @@ describe('BRC-20', () => { test('tokens using order_by tx_count', async () => { // Setup - const inscriptionNumbers = incrementing(1); - const blockHeights = incrementing(775600); + const inscriptionNumbers = incrementing(0); + const blockHeights = incrementing(BRC20_GENESIS_BLOCK); const addressA = 'bc1q6uwuet65rm6xvlz7ztw2gvdmmay5uaycu03mqz'; const addressB = 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'; @@ -2135,7 +2205,7 @@ describe('BRC-20', () => { .transaction({ hash: txHashTransferSend }) .inscriptionTransferred({ inscription_id: `${txHashTransfer}i0`, - updated_address: addressA, + destination: { type: 'transferred', value: addressA }, satpoint_pre_transfer: `${txHashTransfer}:0:0`, satpoint_post_transfer: `${txHashTransferSend}:0:0`, post_transfer_output_value: null, @@ -2249,8 +2319,8 @@ describe('BRC-20', () => { describe('/brc-20/activity', () => { test('activity for token transfers', async () => { // Setup - const inscriptionNumbers = incrementing(1); - const blockHeights = incrementing(775600); + const inscriptionNumbers = incrementing(0); + const blockHeights = incrementing(BRC20_GENESIS_BLOCK); const addressA = 'bc1q6uwuet65rm6xvlz7ztw2gvdmmay5uaycu03mqz'; const addressB = 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'; @@ -2437,7 +2507,7 @@ describe('BRC-20', () => { .block({ height: blockHeights.next().value }) .transaction({ hash: randomHash() }) .inscriptionTransferred({ - updated_address: addressB, + destination: { type: 'transferred', value: addressB }, tx_index: 0, inscription_id: `${transferHash}i0`, post_transfer_output_value: null, @@ -2505,8 +2575,8 @@ describe('BRC-20', () => { // Step 7: B transfer_send the transfer to C // Setup - const inscriptionNumbers = incrementing(1); - const blockHeights = incrementing(775600); + const inscriptionNumbers = incrementing(0); + const blockHeights = incrementing(BRC20_GENESIS_BLOCK); const addressA = 'bc1q6uwuet65rm6xvlz7ztw2gvdmmay5uaycu03mqz'; const addressB = 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'; const addressC = 'bc1q9d80h0q5d3f54w7w8c3l2sguf9uset4ydw9xj2'; @@ -2788,7 +2858,7 @@ describe('BRC-20', () => { .block({ height: blockHeights.next().value }) .transaction({ hash: transferHashABSend }) .inscriptionTransferred({ - updated_address: addressB, + destination: { type: 'transferred', value: addressB }, tx_index: 0, inscription_id: `${transferHashAB}i0`, post_transfer_output_value: null, @@ -2876,7 +2946,7 @@ describe('BRC-20', () => { .block({ height: blockHeights.next().value }) .transaction({ hash: transferHashBCSend }) .inscriptionTransferred({ - updated_address: addressC, + destination: { type: 'transferred', value: addressC }, tx_index: 0, inscription_id: `${transferHashBC}i0`, post_transfer_output_value: null, @@ -2958,8 +3028,8 @@ describe('BRC-20', () => { }); test('activity for multiple token creation', async () => { - const inscriptionNumbers = incrementing(1); - const blockHeights = incrementing(775600); + const inscriptionNumbers = incrementing(0); + const blockHeights = incrementing(BRC20_GENESIS_BLOCK); const addressA = 'bc1q6uwuet65rm6xvlz7ztw2gvdmmay5uaycu03mqz'; // Step 1: Create a token PEPE @@ -3051,7 +3121,7 @@ describe('BRC-20', () => { // Verify that no events are available before the first block height response = await fastify.inject({ method: 'GET', - url: `/ordinals/brc-20/activity?ticker=PEER&block_height=${775600}`, + url: `/ordinals/brc-20/activity?ticker=PEER&block_height=${BRC20_GENESIS_BLOCK}`, }); expect(response.statusCode).toBe(200); json = response.json(); @@ -3061,7 +3131,7 @@ describe('BRC-20', () => { // Verify that the PEER deploy is not in the activity feed when using block_height parameter response = await fastify.inject({ method: 'GET', - url: `/ordinals/brc-20/activity?block_height=${775600}`, + url: `/ordinals/brc-20/activity?block_height=${BRC20_GENESIS_BLOCK}`, }); expect(response.statusCode).toBe(200); json = response.json(); @@ -3097,7 +3167,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775619, + height: BRC20_GENESIS_BLOCK + 2, hash: '0000000000000000000034dd2daec375371800da441b17651459b2220cbc1a6e', }) .transaction({ @@ -3111,7 +3181,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '2000', }, - number: 999, + number: 2, tx_id: '633648e0e1ddcab8dea0496a561f2b08c486ae619b5634d7bb55d7f0cd32ef16', address: 'bc1qp9jgp9qtlhgvwjnxclj6kav6nr2fq09c206pyl', }) @@ -3143,7 +3213,7 @@ describe('BRC-20', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: BRC20_GENESIS_BLOCK, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', }) .transaction({ @@ -3157,7 +3227,7 @@ describe('BRC-20', () => { tick: 'PEPE', max: '250000', }, - number: 5, + number: 0, tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', address: 'bc1qp9jgp9qtlhgvwjnxclj6kav6nr2fq09c206pyl', }) @@ -3194,7 +3264,7 @@ describe('BRC-20', () => { const transferPEPE = new TestChainhookPayloadBuilder() .apply() .block({ - height: 775619, + height: BRC20_GENESIS_BLOCK + 2, hash: '00000000000000000002b14f0c5dde0b2fc74d022e860696bd64f1f652756674', }) .transaction({ @@ -3208,7 +3278,7 @@ describe('BRC-20', () => { tick: 'PEPE', amt: '9000', }, - number: 7, + number: 2, tx_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a', address: address, }) @@ -3218,7 +3288,7 @@ describe('BRC-20', () => { const sendPEPE = new TestChainhookPayloadBuilder() .apply() .block({ - height: 775620, + height: BRC20_GENESIS_BLOCK + 3, hash: '000000000000000000016ddf56d0fe72476165acee9500d48d3e2aaf8412f489', }) .transaction({ @@ -3226,7 +3296,7 @@ describe('BRC-20', () => { }) .inscriptionTransferred({ inscription_id: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47ai0', - updated_address: address2, + destination: { type: 'transferred', value: address2 }, satpoint_pre_transfer: 'eee52b22397ea4a4aefe6a39931315e93a157091f5a994216c0aa9c8c6fef47a:0:0', satpoint_post_transfer: @@ -3240,7 +3310,7 @@ describe('BRC-20', () => { const deployFIRE = new TestChainhookPayloadBuilder() .apply() .block({ - height: 775621, + height: BRC20_GENESIS_BLOCK + 4, hash: '000000000000000000033b0b78ff68c5767109f45ee42696bd4db9b2845a7ea8', }) .transaction({ @@ -3254,7 +3324,7 @@ describe('BRC-20', () => { tick: '🔥', max: '1000', }, - number: 50, + number: 3, tx_id: '8354e85e87fa2df8b3a06ec0b9d395559b95174530cb19447fc4df5f6d4ca84d', address: address, }) @@ -3264,7 +3334,7 @@ describe('BRC-20', () => { const mintFIRE = new TestChainhookPayloadBuilder() .apply() .block({ - height: 775622, + height: BRC20_GENESIS_BLOCK + 5, hash: '00000000000000000001f022fadbd930ccf6acbe00a07626e3a0898fb5799bc9', }) .transaction({ @@ -3278,7 +3348,7 @@ describe('BRC-20', () => { tick: '🔥', amt: '500', }, - number: 60, + number: 4, tx_id: '81f4ee2c247c5f5c0d3a6753fef706df410ea61c2aa6d370003b98beb041b887', address: address, }) @@ -3289,7 +3359,7 @@ describe('BRC-20', () => { const transferFIRE = new TestChainhookPayloadBuilder() .apply() .block({ - height: 775623, + height: BRC20_GENESIS_BLOCK + 6, hash: '00000000000000000002bfcb8860d4730fcd3986b026b9629ea6106fe2cb9197', }) .transaction({ @@ -3303,7 +3373,7 @@ describe('BRC-20', () => { tick: '🔥', amt: '100', }, - number: 90, + number: 5, tx_id: 'c1c7f1d5c10a30605a8a5285ca3465a4f75758ed9b7f201e5ef62727e179966f', address: address, }) @@ -3313,7 +3383,7 @@ describe('BRC-20', () => { const sendFIRE = new TestChainhookPayloadBuilder() .apply() .block({ - height: 775624, + height: BRC20_GENESIS_BLOCK + 7, hash: '00000000000000000003cbbe6d21f03f531cee6e96f33f4a8277a3d8bce5c759', }) .transaction({ @@ -3321,7 +3391,7 @@ describe('BRC-20', () => { }) .inscriptionTransferred({ inscription_id: 'c1c7f1d5c10a30605a8a5285ca3465a4f75758ed9b7f201e5ef62727e179966fi0', - updated_address: address, // To self + destination: { type: 'transferred', value: address }, // To self satpoint_pre_transfer: 'c1c7f1d5c10a30605a8a5285ca3465a4f75758ed9b7f201e5ef62727e179966f:0:0', satpoint_post_transfer: @@ -3395,7 +3465,7 @@ describe('BRC-20', () => { expect(json.results[0].operation).toBe('transfer_send'); request = await fastify.inject({ method: 'GET', - url: `/ordinals/brc-20/activity?block_height=775622`, + url: `/ordinals/brc-20/activity?block_height=${BRC20_GENESIS_BLOCK + 5}`, }); json = request.json(); expect(json.total).toBe(1); @@ -3490,7 +3560,7 @@ describe('BRC-20', () => { expect(json.results[0].operation).toBe('deploy'); request = await fastify.inject({ method: 'GET', - url: `/ordinals/brc-20/activity?block_height=775622`, + url: `/ordinals/brc-20/activity?block_height=${BRC20_GENESIS_BLOCK + 5}`, }); json = request.json(); expect(json.total).toBe(0); diff --git a/tests/cache.test.ts b/tests/cache.test.ts index e789418a..29c890eb 100644 --- a/tests/cache.test.ts +++ b/tests/cache.test.ts @@ -1,4 +1,4 @@ -import { cycleMigrations } from '@hirosystems/api-toolkit'; +import { runMigrations } from '@hirosystems/api-toolkit'; import { buildApiServer } from '../src/api/init'; import { MIGRATIONS_DIR, PgStore } from '../src/pg/pg-store'; import { TestChainhookPayloadBuilder, TestFastifyServer, randomHash } from './helpers'; @@ -8,14 +8,15 @@ describe('ETag cache', () => { let fastify: TestFastifyServer; beforeEach(async () => { + await runMigrations(MIGRATIONS_DIR, 'up'); db = await PgStore.connect({ skipMigrations: true }); fastify = await buildApiServer({ db }); - await cycleMigrations(MIGRATIONS_DIR); }); afterEach(async () => { await fastify.close(); await db.close(); + await runMigrations(MIGRATIONS_DIR, 'down'); }); test('inscription cache control', async () => { @@ -27,7 +28,7 @@ describe('ETag cache', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -54,7 +55,7 @@ describe('ETag cache', () => { // Check on numbered id too const nResponse = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions/7', + url: '/ordinals/v1/inscriptions/0', }); expect(nResponse.statusCode).toBe(200); expect(nResponse.headers.etag).not.toBeUndefined(); @@ -70,7 +71,7 @@ describe('ETag cache', () => { expect(cached.statusCode).toBe(304); const nCached = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions/7', + url: '/ordinals/v1/inscriptions/0', headers: { 'if-none-match': etag1 }, }); expect(nCached.statusCode).toBe(304); @@ -79,13 +80,16 @@ describe('ETag cache', () => { await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() - .block({ height: 775700, timestamp: 1678122360 }) + .block({ height: 775618, timestamp: 1678122360 }) .transaction({ hash: '0xbdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444', }) .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + destination: { + type: 'transferred', + value: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + }, satpoint_pre_transfer: 'da2da520f055e9fadaf1a78b3e01bc53596dcbb88e9c9f53bcb61b98310b1006:0:0', satpoint_post_transfer: @@ -103,7 +107,7 @@ describe('ETag cache', () => { expect(cached2.statusCode).toBe(200); const nCached2 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions/7', + url: '/ordinals/v1/inscriptions/0', headers: { 'if-none-match': etag1 }, }); expect(nCached2.statusCode).toBe(200); @@ -113,13 +117,16 @@ describe('ETag cache', () => { await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() - .block({ height: 775690, timestamp: 1678122360 }) + .block({ height: 775619, timestamp: 1678122360 }) .transaction({ hash: 'bebb1357c97d2348eb8ef24e1d8639ff79c8847bf12999ca7fef463489b40f0f', }) .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + destination: { + type: 'transferred', + value: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + }, satpoint_pre_transfer: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', satpoint_post_transfer: @@ -137,7 +144,7 @@ describe('ETag cache', () => { expect(cached3.statusCode).toBe(200); const nCached3 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions/7', + url: '/ordinals/v1/inscriptions/0', headers: { 'if-none-match': etag2 }, }); expect(nCached3.statusCode).toBe(200); @@ -152,7 +159,7 @@ describe('ETag cache', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -170,13 +177,13 @@ describe('ETag cache', () => { await db.updateInscriptions(block1); const block2 = new TestChainhookPayloadBuilder() .apply() - .block({ height: 775617 }) + .block({ height: 778576 }) .transaction({ hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d' }) .inscriptionRevealed({ content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 2, + inscription_number: 1, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -213,11 +220,14 @@ describe('ETag cache', () => { // New location const block3 = new TestChainhookPayloadBuilder() .apply() - .block({ height: 775618 }) + .block({ height: 778577 }) .transaction({ hash: 'ae9d273a10e899f0d2cad47ee2b0e77ab8a9addd9dd5bb5e4b03d6971c060d52' }) .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + destination: { + type: 'transferred', + value: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + }, satpoint_pre_transfer: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', satpoint_post_transfer: @@ -233,39 +243,6 @@ describe('ETag cache', () => { headers: { 'if-none-match': etag }, }); expect(cached2.statusCode).toBe(200); - const etag2 = cached2.headers.etag; - - // Upsert genesis location - const block4 = new TestChainhookPayloadBuilder() - .apply() - .block({ height: 778575 }) - .transaction({ hash: '0x9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201' }) - .inscriptionRevealed({ - content_bytes: '0x48656C6C6F', - content_type: 'text/plain', - 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', - inscription_input_index: 0, - transfers_pre_inscription: 0, - tx_index: 0, - }) - .build(); - await db.updateInscriptions(block4); - const cached3 = await fastify.inject({ - method: 'GET', - url: '/ordinals/v1/inscriptions', - headers: { 'if-none-match': etag2 }, - }); - expect(cached3.statusCode).toBe(200); }); test('inscriptions stats per block cache control', async () => { @@ -277,7 +254,7 @@ describe('ETag cache', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -314,13 +291,13 @@ describe('ETag cache', () => { // New block const block2 = new TestChainhookPayloadBuilder() .apply() - .block({ height: 778577, hash: randomHash() }) + .block({ height: 778576, hash: randomHash() }) .transaction({ hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d' }) .inscriptionRevealed({ content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 2, + inscription_number: 1, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -355,7 +332,7 @@ describe('ETag cache', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -392,13 +369,13 @@ describe('ETag cache', () => { // New block const block2 = new TestChainhookPayloadBuilder() .apply() - .block({ height: 778577, hash: randomHash() }) + .block({ height: 778576, hash: randomHash() }) .transaction({ hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d' }) .inscriptionRevealed({ content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 2, + inscription_number: 1, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, diff --git a/tests/inscriptions.test.ts b/tests/inscriptions.test.ts index bb42410d..4fbf4255 100644 --- a/tests/inscriptions.test.ts +++ b/tests/inscriptions.test.ts @@ -1,4 +1,4 @@ -import { cycleMigrations } from '@hirosystems/api-toolkit'; +import { runMigrations } from '@hirosystems/api-toolkit'; import { buildApiServer } from '../src/api/init'; import { MIGRATIONS_DIR, PgStore } from '../src/pg/pg-store'; import { TestChainhookPayloadBuilder, TestFastifyServer, rollBack } from './helpers'; @@ -12,14 +12,15 @@ describe('/inscriptions', () => { let fastify: TestFastifyServer; beforeEach(async () => { + await runMigrations(MIGRATIONS_DIR, 'up'); db = await PgStore.connect({ skipMigrations: true }); fastify = await buildApiServer({ db }); - await cycleMigrations(MIGRATIONS_DIR); }); afterEach(async () => { await fastify.close(); await db.close(); + await runMigrations(MIGRATIONS_DIR, 'down'); }); describe('show', () => { @@ -39,7 +40,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 188, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -59,7 +60,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -70,7 +71,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 7, + inscription_number: 1, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -90,14 +91,14 @@ describe('/inscriptions', () => { address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', genesis_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', genesis_block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - genesis_block_height: 775617, + genesis_block_height: 778576, content_length: 5, mime_type: 'image/png', content_type: 'image/png', genesis_fee: '2805', id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', offset: '0', - number: 7, + number: 1, value: '10000', tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', sat_ordinal: '257418248345364', @@ -124,7 +125,7 @@ describe('/inscriptions', () => { // By inscription number const response2 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions/7', + url: '/ordinals/v1/inscriptions/1', }); expect(response2.statusCode).toBe(200); expect(response2.json()).toStrictEqual(expected); @@ -146,7 +147,7 @@ describe('/inscriptions', () => { content_bytes: `0x${Buffer.from('World').toString('hex')}`, content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 188, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -167,7 +168,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 189, + inscription_number: 1, inscription_fee: 2805, inscription_id: 'f351d86c6e6cae3c64e297e7463095732f216875bcc1f3c03f950a492bb25421i0', inscription_output_value: 10000, @@ -187,7 +188,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -204,7 +205,7 @@ describe('/inscriptions', () => { ).toString('hex')}`, content_type: 'image/png', content_length: 5, - inscription_number: 7, + inscription_number: 2, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -224,14 +225,14 @@ describe('/inscriptions', () => { address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', genesis_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', genesis_block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - genesis_block_height: 775617, + genesis_block_height: 778576, content_length: 5, mime_type: 'image/png', content_type: 'image/png', genesis_fee: '2805', id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', offset: '0', - number: 7, + number: 2, value: '10000', tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', sat_ordinal: '257418248345364', @@ -261,53 +262,10 @@ describe('/inscriptions', () => { // By inscription number const response2 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions/7', + url: '/ordinals/v1/inscriptions/2', }); expect(response2.statusCode).toBe(200); expect(response2.json()).toStrictEqual(expected); - - // Backfill new inscription - await db.updateInscriptions( - new TestChainhookPayloadBuilder() - .apply() - .block({ - height: 778600, - hash: '000000000000000000043b10697970720b44c79f6ca2dd604cc83cc015e0c459', - timestamp: 1676913207, - }) - .transaction({ - hash: 'b4b27b9a15f928b95a8ce4b418946553b7b313a345254cd9b23d79489175fa5a', - }) - .inscriptionRevealed({ - content_bytes: `0x${Buffer.from('World').toString('hex')}`, - content_type: 'text/plain;charset=utf-8', - content_length: 5, - inscription_number: 200, - inscription_fee: 705, - inscription_id: 'b4b27b9a15f928b95a8ce4b418946553b7b313a345254cd9b23d79489175fa5ai0', - inscription_output_value: 10000, - inscriber_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', - ordinal_number: 257418248345364, - ordinal_block_height: 650000, - ordinal_offset: 0, - satpoint_post_inscription: - 'b4b27b9a15f928b95a8ce4b418946553b7b313a345254cd9b23d79489175fa5a:0:0', - tx_index: 0, - inscription_input_index: 0, - transfers_pre_inscription: 0, - }) - .build() - ); - const response3 = await fastify.inject({ - method: 'GET', - url: '/ordinals/v1/inscriptions/38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - }); - expect(response3.statusCode).toBe(200); - expect(response3.json().recursion_refs).toStrictEqual([ - '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', - 'f351d86c6e6cae3c64e297e7463095732f216875bcc1f3c03f950a492bb25421i0', - 'b4b27b9a15f928b95a8ce4b418946553b7b313a345254cd9b23d79489175fa5ai0', - ]); }); test('shows inscription with null genesis address', async () => { @@ -326,7 +284,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -353,7 +311,7 @@ describe('/inscriptions', () => { genesis_fee: '2805', id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', offset: '0', - number: 7, + number: 0, value: '10000', tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', sat_ordinal: '257418248345364', @@ -380,7 +338,7 @@ describe('/inscriptions', () => { // By inscription number const response2 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions/7', + url: '/ordinals/v1/inscriptions/0', }); expect(response2.statusCode).toBe(200); expect(response2.json()).toStrictEqual(expected); @@ -479,7 +437,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -506,7 +464,10 @@ describe('/inscriptions', () => { }) .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + destination: { + type: 'transferred', + value: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + }, satpoint_pre_transfer: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', satpoint_post_transfer: @@ -532,7 +493,7 @@ describe('/inscriptions', () => { genesis_fee: '2805', id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', offset: '0', - number: 7, + number: 0, value: '9000', tx_id: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444', sat_ordinal: '257418248345364', @@ -558,7 +519,10 @@ describe('/inscriptions', () => { }) .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1pkjq7cerr6h53qm86k9t3dq0gqg8lcfz5jx7z4aj2mpqrjggrnass0u7qqj', + destination: { + type: 'transferred', + value: 'bc1pkjq7cerr6h53qm86k9t3dq0gqg8lcfz5jx7z4aj2mpqrjggrnass0u7qqj', + }, satpoint_pre_transfer: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444:0:0', satpoint_post_transfer: @@ -584,7 +548,7 @@ describe('/inscriptions', () => { genesis_fee: '2805', id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', offset: '0', - number: 7, + number: 0, tx_id: 'e3af144354367de58c675e987febcb49f17d6c19e645728b833fe95408feab85', value: '8000', sat_ordinal: '257418248345364', @@ -601,124 +565,6 @@ describe('/inscriptions', () => { }); }); - test('shows correct inscription data after an unordered transfer', async () => { - 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: 9000, - inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - ordinal_number: 257418248345364, - ordinal_block_height: 51483, - ordinal_offset: 0, - satpoint_post_inscription: - '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', - inscription_input_index: 0, - transfers_pre_inscription: 0, - tx_index: 0, - }) - .build() - ); - - const response1 = await fastify.inject({ - method: 'GET', - url: '/ordinals/v1/inscriptions/38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - }); - expect(response1.statusCode).toBe(200); - expect(response1.json()).toStrictEqual({ - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - genesis_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - genesis_block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - genesis_block_height: 775617, - content_length: 5, - mime_type: 'image/png', - content_type: 'image/png', - genesis_fee: '2805', - id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - offset: '0', - number: 7, - value: '9000', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - sat_ordinal: '257418248345364', - sat_coinbase_height: 51483, - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - location: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', - sat_rarity: 'common', - timestamp: 1676913207000, - genesis_timestamp: 1676913207000, - genesis_tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - curse_type: null, - recursive: false, - recursion_refs: null, - }); - - // Insert real genesis - await db.updateInscriptions( - new TestChainhookPayloadBuilder() - .apply() - .block({ height: 775610, timestamp: 1678122360 }) - .transaction({ - hash: '0xbdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444', - }) - .inscriptionTransferred({ - inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', - satpoint_pre_transfer: - '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', - satpoint_post_transfer: - 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444:0:0', - post_transfer_output_value: 9000, - tx_index: 0, - }) - .build() - ); - const response = await fastify.inject({ - method: 'GET', - url: '/ordinals/v1/inscriptions/38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - }); - expect(response.statusCode).toBe(200); - expect(response.json()).toStrictEqual({ - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - genesis_address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', - genesis_block_hash: '163de66dc9c0949905bfe8e148bde04600223cf88d19f26fdbeba1d6e6fa0f88', - genesis_block_height: 775610, - content_length: 5, - mime_type: 'image/png', - content_type: 'image/png', - genesis_fee: '2805', - id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - offset: '0', - number: 7, - value: '9000', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', - sat_ordinal: '257418248345364', - sat_coinbase_height: 51483, - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - location: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', - sat_rarity: 'common', - timestamp: 1676913207000, - genesis_timestamp: 1678122360000, - genesis_tx_id: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444', - curse_type: null, - recursive: false, - recursion_refs: null, - }); - }); - test('shows correct data when multiple transfers happen in the same block', async () => { await db.updateInscriptions( new TestChainhookPayloadBuilder() @@ -735,7 +581,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 9000, @@ -763,7 +609,10 @@ describe('/inscriptions', () => { // Transfer 1 .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1qv7d2dgyvtctv7ya4t3ysy4c2s8qz4nm8t6dvm3', + destination: { + type: 'transferred', + value: 'bc1qv7d2dgyvtctv7ya4t3ysy4c2s8qz4nm8t6dvm3', + }, satpoint_pre_transfer: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444:0:0', satpoint_post_transfer: @@ -774,7 +623,10 @@ describe('/inscriptions', () => { // Transfer 2 .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + destination: { + type: 'transferred', + value: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + }, satpoint_pre_transfer: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', satpoint_post_transfer: @@ -800,7 +652,7 @@ describe('/inscriptions', () => { genesis_fee: '2805', id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', offset: '0', - number: 7, + number: 0, value: '8000', tx_id: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444', sat_ordinal: '257418248345364', @@ -861,7 +713,10 @@ describe('/inscriptions', () => { }) .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + destination: { + type: 'transferred', + value: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + }, satpoint_pre_transfer: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', satpoint_post_transfer: @@ -913,7 +768,10 @@ describe('/inscriptions', () => { }) .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1pkjq7cerr6h53qm86k9t3dq0gqg8lcfz5jx7z4aj2mpqrjggrnass0u7qqj', + destination: { + type: 'transferred', + value: 'bc1pkjq7cerr6h53qm86k9t3dq0gqg8lcfz5jx7z4aj2mpqrjggrnass0u7qqj', + }, satpoint_pre_transfer: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444:0:0', satpoint_post_transfer: @@ -974,7 +832,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -1005,7 +863,10 @@ describe('/inscriptions', () => { }) .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + destination: { + type: 'transferred', + value: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + }, satpoint_pre_transfer: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', satpoint_post_transfer: @@ -1061,7 +922,10 @@ describe('/inscriptions', () => { }) .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1pkjq7cerr6h53qm86k9t3dq0gqg8lcfz5jx7z4aj2mpqrjggrnass0u7qqj', + destination: { + type: 'transferred', + value: 'bc1pkjq7cerr6h53qm86k9t3dq0gqg8lcfz5jx7z4aj2mpqrjggrnass0u7qqj', + }, satpoint_pre_transfer: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444:0:0', satpoint_post_transfer: @@ -1131,7 +995,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -1152,7 +1016,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 8, + inscription_number: 1, inscription_fee: 2805, inscription_id: '7ac73ecd01b9da4a7eab904655416dbfe8e03f193e091761b5a63ad0963570cdi0', inscription_output_value: 10000, @@ -1193,7 +1057,10 @@ describe('/inscriptions', () => { }) .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + destination: { + type: 'transferred', + value: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + }, satpoint_pre_transfer: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', satpoint_post_transfer: @@ -1206,7 +1073,10 @@ describe('/inscriptions', () => { }) .inscriptionTransferred({ inscription_id: '7ac73ecd01b9da4a7eab904655416dbfe8e03f193e091761b5a63ad0963570cdi0', - updated_address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + destination: { + type: 'transferred', + value: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + }, satpoint_pre_transfer: '7ac73ecd01b9da4a7eab904655416dbfe8e03f193e091761b5a63ad0963570cd:0:0', satpoint_post_transfer: @@ -1226,7 +1096,7 @@ describe('/inscriptions', () => { expect(json2.results).toStrictEqual([ { id: '7ac73ecd01b9da4a7eab904655416dbfe8e03f193e091761b5a63ad0963570cdi0', - number: 8, + number: 1, from: { address: 'bc1ptrehxtus25xx8jp5pchljxg2aps7mdemc4264zzzsdcvs6q25hhsf3rrph', block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', @@ -1252,7 +1122,7 @@ describe('/inscriptions', () => { }, { id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - number: 7, + number: 0, from: { address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', @@ -1292,7 +1162,10 @@ describe('/inscriptions', () => { }) .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1pkx5me775s748lzchytzdsw4f0lq04wssxnyk27g8fn3gee8zhjjqsn9tfp', + destination: { + type: 'transferred', + value: 'bc1pkx5me775s748lzchytzdsw4f0lq04wssxnyk27g8fn3gee8zhjjqsn9tfp', + }, satpoint_pre_transfer: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444:0:0', satpoint_post_transfer: @@ -1303,7 +1176,10 @@ describe('/inscriptions', () => { // Transfer for same inscription in same block .inscriptionTransferred({ inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1pkx5me775s748lzchytzdsw4f0lq04wssxnyk27g8fn3gee8zhjjqsn9tfp', + destination: { + type: 'transferred', + value: 'bc1pkx5me775s748lzchytzdsw4f0lq04wssxnyk27g8fn3gee8zhjjqsn9tfp', + }, satpoint_pre_transfer: '5cabafe04aaf98b1f325b0c3ffcbff904dbdb6f3d2e9e451102fda36f1056b5e:0:0', satpoint_post_transfer: @@ -1320,10 +1196,75 @@ describe('/inscriptions', () => { expect(response3.statusCode).toBe(200); const json3 = response3.json(); expect(json3.total).toBe(2); + expect(json3.results).toHaveLength(2); expect(json3.results).toStrictEqual([ { id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - number: 7, + number: 0, + from: { + address: 'bc1pkx5me775s748lzchytzdsw4f0lq04wssxnyk27g8fn3gee8zhjjqsn9tfp', + block_hash: '000000000000000000044b12039abd3112963959d9fd7510ac503ea84dc17002', + block_height: 775701, + location: '5cabafe04aaf98b1f325b0c3ffcbff904dbdb6f3d2e9e451102fda36f1056b5e:0:0', + offset: '0', + output: '5cabafe04aaf98b1f325b0c3ffcbff904dbdb6f3d2e9e451102fda36f1056b5e:0', + timestamp: 1676913208000, + tx_id: '5cabafe04aaf98b1f325b0c3ffcbff904dbdb6f3d2e9e451102fda36f1056b5e', + value: '8000', + }, + to: { + address: 'bc1pkx5me775s748lzchytzdsw4f0lq04wssxnyk27g8fn3gee8zhjjqsn9tfp', + block_hash: '000000000000000000044b12039abd3112963959d9fd7510ac503ea84dc17002', + block_height: 775701, + location: '5cabafe04aaf98b1f325b0c3ffcbff904dbdb6f3d2e9e451102fda36f1056b5e:1:0', + offset: '0', + output: '5cabafe04aaf98b1f325b0c3ffcbff904dbdb6f3d2e9e451102fda36f1056b5e:1', + timestamp: 1676913208000, + tx_id: '5cabafe04aaf98b1f325b0c3ffcbff904dbdb6f3d2e9e451102fda36f1056b5e', + value: '7500', + }, + }, + { + id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + number: 0, + from: { + address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', + block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7bbbb', + block_height: 775700, + location: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444:0:0', + offset: '0', + output: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444:0', + timestamp: 1678122360000, + tx_id: 'bdda0d240132bab2af7f797d1507beb1acab6ad43e2c0ef7f96291aea5cc3444', + value: '9000', + }, + to: { + address: 'bc1pkx5me775s748lzchytzdsw4f0lq04wssxnyk27g8fn3gee8zhjjqsn9tfp', + block_hash: '000000000000000000044b12039abd3112963959d9fd7510ac503ea84dc17002', + block_height: 775701, + location: '5cabafe04aaf98b1f325b0c3ffcbff904dbdb6f3d2e9e451102fda36f1056b5e:0:0', + offset: '0', + output: '5cabafe04aaf98b1f325b0c3ffcbff904dbdb6f3d2e9e451102fda36f1056b5e:0', + timestamp: 1676913208000, + tx_id: '5cabafe04aaf98b1f325b0c3ffcbff904dbdb6f3d2e9e451102fda36f1056b5e', + value: '8000', + }, + }, + ]); + + // Test pagination + const response4 = await fastify.inject({ + method: 'GET', + url: '/ordinals/v1/inscriptions/transfers?block=775701&limit=1&offset=0', + }); + expect(response4.statusCode).toBe(200); + const json4 = response4.json(); + expect(json4.total).toBe(2); + expect(json4.results).toHaveLength(1); + expect(json4.results).toStrictEqual([ + { + id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + number: 0, from: { address: 'bc1pkx5me775s748lzchytzdsw4f0lq04wssxnyk27g8fn3gee8zhjjqsn9tfp', block_hash: '000000000000000000044b12039abd3112963959d9fd7510ac503ea84dc17002', @@ -1347,9 +1288,19 @@ describe('/inscriptions', () => { value: '7500', }, }, + ]); + const response5 = await fastify.inject({ + method: 'GET', + url: '/ordinals/v1/inscriptions/transfers?block=775701&limit=1&offset=1', + }); + expect(response5.statusCode).toBe(200); + const json5 = response5.json(); + expect(json5.total).toBe(2); + expect(json5.results).toHaveLength(1); + expect(json5.results).toStrictEqual([ { id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - number: 7, + number: 0, from: { address: 'bc1p3xqwzmddceqrd6x9yxplqzkl5vucta2gqm5szpkmpuvcvgs7g8psjf8htd', block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7bbbb', @@ -1394,7 +1345,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -1414,7 +1365,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -1451,53 +1402,53 @@ describe('/inscriptions', () => { expect(responseJson1.total).toBe(2); expect(responseJson1.results).toStrictEqual([ { - address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', - genesis_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + genesis_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', genesis_block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - genesis_block_height: 778575, + genesis_block_height: 778576, content_length: 5, - mime_type: 'text/plain', - content_type: 'text/plain;charset=utf-8', - genesis_fee: '705', - id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', + mime_type: 'image/png', + content_type: 'image/png', + genesis_fee: '2805', + id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', offset: '0', - number: 7, + number: 1, value: '10000', sat_ordinal: '257418248345364', + tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', sat_coinbase_height: 51483, - output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', - location: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', + output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', + location: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', sat_rarity: 'common', - tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', timestamp: 1676913207000, genesis_timestamp: 1676913207000, - genesis_tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', + genesis_tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', curse_type: null, recursive: false, recursion_refs: null, }, { - address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - genesis_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + genesis_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', genesis_block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - genesis_block_height: 775617, + genesis_block_height: 778575, content_length: 5, - mime_type: 'image/png', - content_type: 'image/png', - genesis_fee: '2805', - id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + mime_type: 'text/plain', + content_type: 'text/plain;charset=utf-8', + genesis_fee: '705', + id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', offset: '0', - number: 1, + number: 0, value: '10000', sat_ordinal: '257418248345364', - tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', sat_coinbase_height: 51483, - output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0', - location: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + output: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0', + location: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', sat_rarity: 'common', + tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', timestamp: 1676913207000, genesis_timestamp: 1676913207000, - genesis_tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + genesis_tx_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', curse_type: null, recursive: false, recursion_refs: null, @@ -1522,7 +1473,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -1542,7 +1493,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -1588,7 +1539,7 @@ describe('/inscriptions', () => { genesis_fee: '705', id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', offset: '0', - number: 7, + number: 0, value: '10000', sat_ordinal: '257418248345364', sat_coinbase_height: 51483, @@ -1616,7 +1567,7 @@ describe('/inscriptions', () => { address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', genesis_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', genesis_block_hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - genesis_block_height: 775617, + genesis_block_height: 778576, content_length: 5, mime_type: 'image/png', content_type: 'image/png', @@ -1647,8 +1598,8 @@ describe('/inscriptions', () => { expect(response3.statusCode).toBe(200); const responseJson3 = response3.json(); expect(responseJson3.total).toBe(2); - expect(responseJson3.results[0]).toStrictEqual(result1); - expect(responseJson3.results[1]).toStrictEqual(result2); + expect(responseJson3.results[0]).toStrictEqual(result2); + expect(responseJson3.results[1]).toStrictEqual(result1); }); test('index filtered by sat rarity', async () => { @@ -1667,7 +1618,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -1687,7 +1638,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -1758,7 +1709,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -1778,7 +1729,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -1853,7 +1804,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -1873,7 +1824,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -1884,7 +1835,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 50, + inscription_number: 1, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -1903,25 +1854,25 @@ describe('/inscriptions', () => { const response1 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions?number=7', + url: '/ordinals/v1/inscriptions?number=0', }); expect(response1.statusCode).toBe(200); const responseJson1 = response1.json(); expect(responseJson1.total).toBe(1); - expect(responseJson1.results[0].number).toBe(7); + expect(responseJson1.results[0].number).toBe(0); const response2 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions?number=50', + url: '/ordinals/v1/inscriptions?number=1', }); expect(response2.statusCode).toBe(200); const responseJson2 = response2.json(); expect(responseJson2.total).toBe(1); - expect(responseJson2.results[0].number).toBe(50); + expect(responseJson2.results[0].number).toBe(1); const response3 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions?number=7&number=50', + url: '/ordinals/v1/inscriptions?number=0&number=1', }); expect(response3.statusCode).toBe(200); const responseJson3 = response3.json(); @@ -1944,7 +1895,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -1964,7 +1915,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -1994,17 +1945,17 @@ describe('/inscriptions', () => { const response1 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions?genesis_block=775617', + url: '/ordinals/v1/inscriptions?genesis_block=778576', }); expect(response1.statusCode).toBe(200); const responseJson1 = response1.json(); expect(responseJson1.total).toBe(1); expect(responseJson1.results.length).toBe(1); - expect(responseJson1.results[0].genesis_block_height).toBe(775617); + expect(responseJson1.results[0].genesis_block_height).toBe(778576); const response2 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions?from_genesis_block_height=778000', + url: '/ordinals/v1/inscriptions?to_genesis_block_height=778575', }); expect(response2.statusCode).toBe(200); const responseJson2 = response2.json(); @@ -2014,19 +1965,19 @@ describe('/inscriptions', () => { const response3 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions?to_genesis_block_height=778000', + url: '/ordinals/v1/inscriptions?from_genesis_block_height=778576', }); expect(response3.statusCode).toBe(200); const responseJson3 = response3.json(); expect(responseJson3.total).toBe(1); expect(responseJson3.results.length).toBe(1); - expect(responseJson3.results[0].genesis_block_height).toBe(775617); + expect(responseJson3.results[0].genesis_block_height).toBe(778576); await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() .block({ - height: 778580, + height: 778577, hash: '000000000000000000003ac2d5b588bc97a5479d25e403cffd90bd60c9680cfc', timestamp: 1676913207, }) @@ -2037,7 +1988,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 70, + inscription_number: 2, inscription_fee: 705, inscription_id: '25b372de3de0cb6fcc52c89a8bc3fb78eec596521ba20de16e53c1585be7c3fci0', inscription_output_value: 10000, @@ -2055,13 +2006,13 @@ describe('/inscriptions', () => { ); const response4 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions?from_genesis_block_height=778000&to_genesis_block_height=779000', + url: '/ordinals/v1/inscriptions?from_genesis_block_height=778575&to_genesis_block_height=778576', }); expect(response4.statusCode).toBe(200); const responseJson4 = response4.json(); expect(responseJson4.total).toBe(2); expect(responseJson4.results.length).toBe(2); - expect(responseJson4.results[0].genesis_block_height).toBe(778580); + expect(responseJson4.results[0].genesis_block_height).toBe(778576); expect(responseJson4.results[1].genesis_block_height).toBe(778575); }); @@ -2081,7 +2032,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -2101,7 +2052,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -2158,7 +2109,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -2178,7 +2129,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1675312161, }) @@ -2243,7 +2194,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -2263,7 +2214,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1675312161, }) @@ -2328,7 +2279,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -2348,7 +2299,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1675312161, }) @@ -2413,7 +2364,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -2433,7 +2384,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1675312161, }) @@ -2444,7 +2395,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 50, + inscription_number: 1, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -2463,22 +2414,22 @@ describe('/inscriptions', () => { const response2 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions?from_number=10', + url: '/ordinals/v1/inscriptions?from_number=1', }); expect(response2.statusCode).toBe(200); const responseJson2 = response2.json(); expect(responseJson2.total).toBe(1); - expect(responseJson2.results[0].number).toBe(50); + expect(responseJson2.results[0].number).toBe(1); const response3 = await fastify.inject({ method: 'GET', - url: '/ordinals/v1/inscriptions?to_number=10', + url: '/ordinals/v1/inscriptions?to_number=0', }); expect(response3.statusCode).toBe(200); const responseJson3 = response3.json(); expect(responseJson3.total).toBe(1); expect(responseJson3.results.length).toBe(1); - expect(responseJson3.results[0].number).toBe(7); + expect(responseJson3.results[0].number).toBe(0); }); test('index filtered by output', async () => { @@ -2497,7 +2448,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -2517,7 +2468,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1675312161, }) @@ -2562,7 +2513,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -2594,7 +2545,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1675312161, }) @@ -2646,7 +2597,10 @@ describe('/inscriptions', () => { // Transfers affect result totals correctly const transfer2: BitcoinInscriptionTransferred = { inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + destination: { + type: 'transferred', + value: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + }, satpoint_pre_transfer: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', satpoint_post_transfer: @@ -2658,7 +2612,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775618, + height: 778577, hash: '000000000000000000032ef6c45a69c0496456b3cae84ee9f2899f636d03c5ac', timestamp: 1675312161, }) @@ -2715,7 +2669,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .rollback() .block({ - height: 775618, + height: 778577, hash: '000000000000000000032ef6c45a69c0496456b3cae84ee9f2899f636d03c5ac', timestamp: 1675312161, }) @@ -2760,7 +2714,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -2780,7 +2734,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1675312161, }) @@ -2851,7 +2805,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -2871,14 +2825,14 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1675312161, }) .transaction({ hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', }) - .inscriptionRevealed({ + .cursedInscriptionRevealed({ content_bytes: `0x${Buffer.from( 'Hello /content/9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0' ).toString('hex')}`, @@ -2897,6 +2851,7 @@ describe('/inscriptions', () => { inscription_input_index: 0, transfers_pre_inscription: 0, tx_index: 0, + curse_type: 'test', }) .build() ); @@ -2942,7 +2897,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -2961,7 +2916,7 @@ describe('/inscriptions', () => { const genesis2 = new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1675312161, }) @@ -3040,7 +2995,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -3060,7 +3015,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -3071,7 +3026,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 8, + inscription_number: 1, inscription_fee: 705, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -3091,7 +3046,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 778583, + height: 778577, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -3102,7 +3057,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 9, + inscription_number: 2, inscription_fee: 2805, inscription_id: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1i0', inscription_output_value: 10000, @@ -3126,9 +3081,9 @@ describe('/inscriptions', () => { expect(response1.statusCode).toBe(200); const responseJson1 = response1.json(); expect(responseJson1.total).toBe(3); - expect(responseJson1.results[0].number).toStrictEqual(7); - expect(responseJson1.results[1].number).toStrictEqual(8); - expect(responseJson1.results[2].number).toStrictEqual(9); + expect(responseJson1.results[0].number).toStrictEqual(0); + expect(responseJson1.results[1].number).toStrictEqual(1); + expect(responseJson1.results[2].number).toStrictEqual(2); const response2 = await fastify.inject({ method: 'GET', @@ -3137,9 +3092,9 @@ describe('/inscriptions', () => { expect(response2.statusCode).toBe(200); const responseJson2 = response2.json(); expect(responseJson2.total).toBe(3); - expect(responseJson2.results[0].number).toStrictEqual(9); - expect(responseJson2.results[1].number).toStrictEqual(8); - expect(responseJson2.results[2].number).toStrictEqual(7); + expect(responseJson2.results[0].number).toStrictEqual(2); + expect(responseJson2.results[1].number).toStrictEqual(1); + expect(responseJson2.results[2].number).toStrictEqual(0); }); test('index ordered by sat rarity', async () => { @@ -3158,7 +3113,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -3178,7 +3133,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -3189,7 +3144,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 8, + inscription_number: 1, inscription_fee: 705, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -3209,7 +3164,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 778583, + height: 778577, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -3220,7 +3175,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 9, + inscription_number: 2, inscription_fee: 2805, inscription_id: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1i0', inscription_output_value: 10000, @@ -3276,7 +3231,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -3296,7 +3251,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -3307,7 +3262,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 8, + inscription_number: 1, inscription_fee: 705, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -3327,7 +3282,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 778583, + height: 778577, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -3338,7 +3293,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 9, + inscription_number: 2, inscription_fee: 2805, inscription_id: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1i0', inscription_output_value: 10000, @@ -3394,7 +3349,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 705, inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, @@ -3414,7 +3369,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 778576, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -3425,7 +3380,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 8, + inscription_number: 1, inscription_fee: 705, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -3445,7 +3400,7 @@ describe('/inscriptions', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 778583, + height: 778577, hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) @@ -3456,7 +3411,7 @@ describe('/inscriptions', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 9, + inscription_number: 2, inscription_fee: 2805, inscription_id: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1i0', inscription_output_value: 10000, @@ -3480,9 +3435,9 @@ describe('/inscriptions', () => { expect(response1.statusCode).toBe(200); const responseJson1 = response1.json(); expect(responseJson1.total).toBe(3); - expect(responseJson1.results[0].genesis_block_height).toStrictEqual(775617); - expect(responseJson1.results[1].genesis_block_height).toStrictEqual(778575); - expect(responseJson1.results[2].genesis_block_height).toStrictEqual(778583); + expect(responseJson1.results[0].genesis_block_height).toStrictEqual(778575); + expect(responseJson1.results[1].genesis_block_height).toStrictEqual(778576); + expect(responseJson1.results[2].genesis_block_height).toStrictEqual(778577); const response2 = await fastify.inject({ method: 'GET', @@ -3491,56 +3446,9 @@ describe('/inscriptions', () => { expect(response2.statusCode).toBe(200); const responseJson2 = response2.json(); expect(responseJson2.total).toBe(3); - expect(responseJson2.results[0].genesis_block_height).toStrictEqual(778583); - expect(responseJson2.results[1].genesis_block_height).toStrictEqual(778575); - expect(responseJson2.results[2].genesis_block_height).toStrictEqual(775617); - - // Same genesis block items should be sorted by number - await db.updateInscriptions( - new TestChainhookPayloadBuilder() - .apply() - .block({ - height: 778583, - hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - timestamp: 1676913207, - }) - .transaction({ - hash: '907f66261909f40e6b892b7ac574fd9e8661262c83b48be7488194b66523abbd', - }) - .inscriptionRevealed({ - content_bytes: '0x48656C6C6F', - content_type: 'image/png', - content_length: 5, - inscription_number: 43, - inscription_fee: 2805, - inscription_id: '907f66261909f40e6b892b7ac574fd9e8661262c83b48be7488194b66523abbdi0', - inscription_output_value: 10000, - inscriber_address: 'bc1pxq6t85qp57aw8yf8eh9t7vsgd9zm5a8372rdll5jzrmc3cxqdpmqfucdry', - ordinal_number: 0, - ordinal_block_height: 0, - ordinal_offset: 0, - satpoint_post_inscription: - '907f66261909f40e6b892b7ac574fd9e8661262c83b48be7488194b66523abbd:0:0', - inscription_input_index: 0, - transfers_pre_inscription: 0, - tx_index: 0, - }) - .build() - ); - - const response3 = await fastify.inject({ - method: 'GET', - url: '/ordinals/v1/inscriptions?order_by=genesis_block_height&order=desc', - }); - expect(response3.statusCode).toBe(200); - const responseJson3 = response3.json(); - expect(responseJson3.total).toBe(4); - expect(responseJson3.results[0].genesis_block_height).toStrictEqual(778583); - expect(responseJson3.results[0].number).toStrictEqual(43); - expect(responseJson3.results[1].genesis_block_height).toStrictEqual(778583); - expect(responseJson3.results[1].number).toStrictEqual(9); - expect(responseJson3.results[2].genesis_block_height).toStrictEqual(778575); - expect(responseJson3.results[3].genesis_block_height).toStrictEqual(775617); + expect(responseJson2.results[0].genesis_block_height).toStrictEqual(778577); + expect(responseJson2.results[1].genesis_block_height).toStrictEqual(778576); + expect(responseJson2.results[2].genesis_block_height).toStrictEqual(778575); }); }); }); diff --git a/tests/sats.test.ts b/tests/sats.test.ts index 479fee4c..723d23c7 100644 --- a/tests/sats.test.ts +++ b/tests/sats.test.ts @@ -1,4 +1,4 @@ -import { cycleMigrations } from '@hirosystems/api-toolkit'; +import { runMigrations } from '@hirosystems/api-toolkit'; import { buildApiServer } from '../src/api/init'; import { MIGRATIONS_DIR, PgStore } from '../src/pg/pg-store'; import { TestChainhookPayloadBuilder, TestFastifyServer } from './helpers'; @@ -8,14 +8,15 @@ describe('/sats', () => { let fastify: TestFastifyServer; beforeEach(async () => { + await runMigrations(MIGRATIONS_DIR, 'up'); db = await PgStore.connect({ skipMigrations: true }); fastify = await buildApiServer({ db }); - await cycleMigrations(MIGRATIONS_DIR); }); afterEach(async () => { await fastify.close(); await db.close(); + await runMigrations(MIGRATIONS_DIR, 'down'); }); test('returns valid sat', async () => { @@ -48,7 +49,7 @@ describe('/sats', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -105,7 +106,7 @@ describe('/sats', () => { new TestChainhookPayloadBuilder() .apply() .block({ - height: 775617, + height: 775618, hash: '000000000000000000002a244dc7dfcf8ab85e42d182531c27197fc125086f19', timestamp: 1676913207, }) @@ -147,7 +148,7 @@ describe('/sats', () => { content_type: 'image/png', genesis_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', genesis_block_hash: '000000000000000000002a244dc7dfcf8ab85e42d182531c27197fc125086f19', - genesis_block_height: 775617, + genesis_block_height: 775618, genesis_fee: '2805', genesis_timestamp: 1676913207000, genesis_tx_id: 'b9cd9489fe30b81d007f753663d12766f1368721a87f4c69056c8215caa57993', diff --git a/tests/server.test.ts b/tests/server.test.ts index 49b8055b..b375d741 100644 --- a/tests/server.test.ts +++ b/tests/server.test.ts @@ -3,12 +3,13 @@ import { ENV } from '../src/env'; import { MIGRATIONS_DIR, PgStore } from '../src/pg/pg-store'; import { TestChainhookPayloadBuilder, TestFastifyServer } from './helpers'; import { + BadPayloadRequestError, BitcoinInscriptionRevealed, BitcoinInscriptionTransferred, ChainhookEventObserver, } from '@hirosystems/chainhook-client'; import { buildApiServer } from '../src/api/init'; -import { cycleMigrations } from '@hirosystems/api-toolkit'; +import { runMigrations } from '@hirosystems/api-toolkit'; describe('EventServer', () => { let db: PgStore; @@ -16,9 +17,9 @@ describe('EventServer', () => { let fastify: TestFastifyServer; beforeEach(async () => { - db = await PgStore.connect({ skipMigrations: true }); - await cycleMigrations(MIGRATIONS_DIR); + await runMigrations(MIGRATIONS_DIR, 'up'); ENV.CHAINHOOK_AUTO_PREDICATE_REGISTRATION = false; + db = await PgStore.connect({ skipMigrations: true }); server = await startChainhookServer({ db }); fastify = await buildApiServer({ db }); }); @@ -27,6 +28,7 @@ describe('EventServer', () => { await server.close(); await fastify.close(); await db.close(); + await runMigrations(MIGRATIONS_DIR, 'down'); }); describe('parser', () => { @@ -35,7 +37,7 @@ describe('EventServer', () => { content_bytes: '0x303030303030303030303030', content_type: 'text/plain;charset=utf-8', content_length: 12, - inscription_number: 100, + inscription_number: 0, inscription_fee: 3425, inscription_output_value: 10000, inscription_id: '0268dd9743c862d80ab02cb1d0228036cfe172522850eb96be60cfee14b31fb8i0', @@ -54,7 +56,7 @@ describe('EventServer', () => { const payload1 = new TestChainhookPayloadBuilder() .apply() .block({ - height: 107, + height: 767430, hash: '0x163de66dc9c0949905bfe8e148bde04600223cf88d19f26fdbeba1d6e6fa0f88', timestamp: 1676913207, }) @@ -89,7 +91,7 @@ describe('EventServer', () => { expect(inscr.genesis_block_hash).toBe( '163de66dc9c0949905bfe8e148bde04600223cf88d19f26fdbeba1d6e6fa0f88' ); - expect(inscr.genesis_block_height).toBe('107'); + expect(inscr.genesis_block_height).toBe('767430'); expect(inscr.genesis_fee).toBe('3425'); expect(inscr.genesis_id).toBe( '0268dd9743c862d80ab02cb1d0228036cfe172522850eb96be60cfee14b31fb8i0' @@ -99,7 +101,7 @@ describe('EventServer', () => { '0268dd9743c862d80ab02cb1d0228036cfe172522850eb96be60cfee14b31fb8' ); expect(inscr.mime_type).toBe('text/plain'); - expect(inscr.number).toBe('100'); + expect(inscr.number).toBe('0'); expect(inscr.offset).toBe('0'); expect(inscr.output).toBe( '0268dd9743c862d80ab02cb1d0228036cfe172522850eb96be60cfee14b31fb8:0' @@ -152,7 +154,7 @@ describe('EventServer', () => { content_bytes: '0x48656C6C6F', content_type: 'image/png', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -171,7 +173,10 @@ describe('EventServer', () => { const transfer: BitcoinInscriptionTransferred = { inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf00000', + destination: { + type: 'transferred', + value: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf00000', + }, satpoint_pre_transfer: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', satpoint_post_transfer: @@ -229,7 +234,7 @@ describe('EventServer', () => { '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc' ); expect(inscr.mime_type).toBe('image/png'); - expect(inscr.number).toBe('7'); + expect(inscr.number).toBe('0'); expect(inscr.offset).toBe('5000'); expect(inscr.output).toBe( '0268dd9743c862d80ab02cb1d0228036cfe172522850eb96be60cfee14b31fb8:0' @@ -266,180 +271,352 @@ describe('EventServer', () => { expect(c2[0].count).toBe(1); }); - test('saves transfer without genesis and fills the gap later', async () => { - // Insert transfers with no genesis + test('multiple inscription pointers on the same block are compared correctly', async () => { + const address = 'bc1q92zytmqgczsrg4xuhpc2asz6h4h7ke5hagw8k6'; + const address2 = 'bc1qtpm0fsaawxjsthfdrxhmrzunnpjx0g9hncgvp7'; await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() .block({ - height: 775620, - hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - timestamp: 1676913207, + height: 808382, + hash: '00000000000000000002b14f0c5dde0b2fc74d022e860696bd64f1f652756674', }) .transaction({ - hash: '0x38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + hash: '6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'image/png', + content_length: 5, + inscription_number: 0, + inscription_fee: 2805, + inscription_id: '6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02i0', + inscription_output_value: 10000, + inscriber_address: address, + ordinal_number: 5, + ordinal_block_height: 0, + ordinal_offset: 0, + satpoint_post_inscription: + '6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02:0:0', + inscription_input_index: 0, + transfers_pre_inscription: 0, + tx_index: 995, + }) + .transaction({ + hash: '7edaa48337a94da327b6262830505f116775a32db5ad4ad46e87ecea33f21bac', }) .inscriptionTransferred({ - inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1qcf3dgqgvylmd5ayl4njm4ephqfdazy93ssu28j', + inscription_id: '6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02i0', + destination: { type: 'transferred', value: address2 }, satpoint_pre_transfer: - '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + '6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02:0:0', satpoint_post_transfer: - '9e2414153b1893f799477f7e1a00a52fafc235de72fd215cb3321f253c4464ac:0:0', - post_transfer_output_value: 9000, - tx_index: 0, + '7edaa48337a94da327b6262830505f116775a32db5ad4ad46e87ecea33f21bac:0:0', + post_transfer_output_value: null, + tx_index: 1019, // '1019' is less than '995' when compared as a string }) .build() ); + const response = await fastify.inject({ + method: 'GET', + url: `/ordinals/inscriptions/6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02i0`, + }); + expect(response.statusCode).toBe(200); + const json = response.json(); + expect(json.genesis_address).toBe(address); + }); + }); + + describe('gap detection', () => { + test('server rejects payload with first inscription gap', async () => { await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() .block({ - height: 775621, - hash: '00000000000000000003dd4738355bedb73796de9b1099e59ff7adc235e967a6', + height: 778575, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) .transaction({ - hash: '2fa1640d61f04a699833f0f6a884f543c835fc60f0fd4da8627ebb857acdce84', + hash: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', }) - .inscriptionTransferred({ - inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - updated_address: 'bc1qcf3dgqgvylmd5ayl4njm4ephqfdazy93ssu28j', - satpoint_pre_transfer: - '9e2414153b1893f799477f7e1a00a52fafc235de72fd215cb3321f253c4464ac:0:0', - satpoint_post_transfer: - '2fa1640d61f04a699833f0f6a884f543c835fc60f0fd4da8627ebb857acdce84:0:0', - post_transfer_output_value: 8000, + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 0, + 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', + inscription_input_index: 0, + transfers_pre_inscription: 0, tx_index: 0, }) .build() ); - // Locations should exist with null FKs - const results1 = await db.sql` - SELECT * FROM locations - WHERE genesis_id = '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0' - `; - expect(results1.count).toBe(2); - expect(results1[0].inscription_id).toBeNull(); - expect(results1[1].inscription_id).toBeNull(); - const api1 = await fastify.inject({ - method: 'GET', - url: '/ordinals/v1/inscriptions/38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - }); - expect(api1.statusCode).toBe(404); - const api2 = await fastify.inject({ - method: 'GET', - url: '/ordinals/v1/inscriptions/38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0/transfers', + const errorPayload = new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778576, + hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 5, // Gap at 5 + 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', + inscription_input_index: 0, + transfers_pre_inscription: 0, + tx_index: 0, + }) + .build(); + await expect(db.updateInscriptions(errorPayload)).rejects.toThrow(BadPayloadRequestError); + const response = await server['fastify'].inject({ + method: 'POST', + url: `/chainhook/${PREDICATE_UUID}`, + headers: { authorization: `Bearer ${ENV.CHAINHOOK_NODE_AUTH_TOKEN}` }, + payload: errorPayload, }); - expect(api2.json().total).toBe(0); + expect(response.statusCode).toBe(400); + }); - // Insert genesis and make sure locations are normalized. + test('server rejects payload with intermediate inscription gap', async () => { await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() .block({ - height: 775618, + height: 778575, hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', timestamp: 1676913207, }) .transaction({ - hash: '0x38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + hash: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', }) .inscriptionRevealed({ content_bytes: '0x48656C6C6F', - content_type: 'image/png', + content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, - inscription_fee: 2805, - inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', + inscription_number: 0, + inscription_fee: 705, + inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, - inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - ordinal_number: 5, - ordinal_block_height: 0, + inscriber_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, ordinal_offset: 0, satpoint_post_inscription: - '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0:0', + '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', inscription_input_index: 0, transfers_pre_inscription: 0, tx_index: 0, }) .build() ); - // Locations should exist with all FKs filled in - const results2 = await db.sql` - SELECT * FROM locations - WHERE genesis_id = '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0' - `; - expect(results2.count).toBe(3); - expect(results2[0].inscription_id).not.toBeNull(); - expect(results2[1].inscription_id).not.toBeNull(); - expect(results2[2].inscription_id).not.toBeNull(); - const api3 = await fastify.inject({ - method: 'GET', - url: '/ordinals/v1/inscriptions/38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', - }); - expect(api3.statusCode).toBe(200); - expect(api3.json().genesis_block_height).toBe(775618); - const api4 = await fastify.inject({ - method: 'GET', - url: '/ordinals/v1/inscriptions/38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0/transfers', + const errorPayload = new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778576, + hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 1, + 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', + inscription_input_index: 0, + transfers_pre_inscription: 0, + tx_index: 0, + }) + .transaction({ + hash: '6891d374a17ba85f6b5514f2f7edc301c1c860284dff5a5c6e88ab3a20fcd8a5', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 4, // Gap + inscription_fee: 705, + inscription_id: '6891d374a17ba85f6b5514f2f7edc301c1c860284dff5a5c6e88ab3a20fcd8a5o0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 1050000000000000, + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '6891d374a17ba85f6b5514f2f7edc301c1c860284dff5a5c6e88ab3a20fcd8a5:0:0', + inscription_input_index: 0, + transfers_pre_inscription: 0, + tx_index: 0, + }) + .build(); + await expect(db.updateInscriptions(errorPayload)).rejects.toThrow(BadPayloadRequestError); + const response = await server['fastify'].inject({ + method: 'POST', + url: `/chainhook/${PREDICATE_UUID}`, + headers: { authorization: `Bearer ${ENV.CHAINHOOK_NODE_AUTH_TOKEN}` }, + payload: errorPayload, }); - expect(api4.json().total).toBe(3); + expect(response.statusCode).toBe(400); }); - test('multiple inscription pointers on the same block are compared correctly', async () => { - const address = 'bc1q92zytmqgczsrg4xuhpc2asz6h4h7ke5hagw8k6'; - const address2 = 'bc1qtpm0fsaawxjsthfdrxhmrzunnpjx0g9hncgvp7'; + test('server accepts payload with unordered unbound inscriptions', async () => { await db.updateInscriptions( new TestChainhookPayloadBuilder() .apply() .block({ - height: 808382, - hash: '00000000000000000002b14f0c5dde0b2fc74d022e860696bd64f1f652756674', + height: 778575, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, }) .transaction({ - hash: '6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02', + hash: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201', }) .inscriptionRevealed({ content_bytes: '0x48656C6C6F', - content_type: 'image/png', + content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 33224825, - inscription_fee: 2805, - inscription_id: '6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02i0', + inscription_number: 0, + inscription_fee: 705, + inscription_id: '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201i0', inscription_output_value: 10000, - inscriber_address: address, - ordinal_number: 5, - ordinal_block_height: 0, + inscriber_address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', + ordinal_number: 257418248345364, + ordinal_block_height: 650000, ordinal_offset: 0, satpoint_post_inscription: - '6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02:0:0', + '9f4a9b73b0713c5da01c0a47f97c6c001af9028d6bdd9e264dfacbc4e6790201:0:0', inscription_input_index: 0, transfers_pre_inscription: 0, - tx_index: 995, - }) - .transaction({ - hash: '7edaa48337a94da327b6262830505f116775a32db5ad4ad46e87ecea33f21bac', - }) - .inscriptionTransferred({ - inscription_id: '6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02i0', - updated_address: address2, - satpoint_pre_transfer: - '6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02:0:0', - satpoint_post_transfer: - '7edaa48337a94da327b6262830505f116775a32db5ad4ad46e87ecea33f21bac:0:0', - post_transfer_output_value: null, - tx_index: 1019, // '1019' is less than '995' when compared as a string + tx_index: 0, }) .build() ); - const response = await fastify.inject({ - method: 'GET', - url: `/ordinals/inscriptions/6046f17804eb8396285567a20c09598ae1273b6f744b23700ba95593c380ce02i0`, + const unboundPayload = new TestChainhookPayloadBuilder() + .apply() + .block({ + height: 778576, + hash: '00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }) + .transaction({ + hash: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 2, + 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', + inscription_input_index: 0, + transfers_pre_inscription: 0, + tx_index: 0, + }) + .transaction({ + hash: '6891d374a17ba85f6b5514f2f7edc301c1c860284dff5a5c6e88ab3a20fcd8a5', + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'text/plain;charset=utf-8', + content_length: 5, + inscription_number: 1, + inscription_fee: 705, + inscription_id: '6891d374a17ba85f6b5514f2f7edc301c1c860284dff5a5c6e88ab3a20fcd8a5o0', + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: 0, // Unbounded + ordinal_block_height: 650000, + ordinal_offset: 0, + satpoint_post_inscription: + '6891d374a17ba85f6b5514f2f7edc301c1c860284dff5a5c6e88ab3a20fcd8a5:0:0', + inscription_input_index: 0, + transfers_pre_inscription: 0, + tx_index: 0, + }) + .build(); + await expect(db.updateInscriptions(unboundPayload)).resolves.not.toThrow( + BadPayloadRequestError + ); + }); + + test('server ignores past blocks', async () => { + const payload = 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: 0, + 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', + inscription_input_index: 0, + transfers_pre_inscription: 0, + tx_index: 0, + }) + .build(); + await db.updateInscriptions(payload); + + const response = await server['fastify'].inject({ + method: 'POST', + url: `/chainhook/${PREDICATE_UUID}`, + headers: { authorization: `Bearer ${ENV.CHAINHOOK_NODE_AUTH_TOKEN}` }, + payload: payload, }); expect(response.statusCode).toBe(200); - const json = response.json(); - expect(json.genesis_address).toBe(address); }); }); }); diff --git a/tests/stats.test.ts b/tests/stats.test.ts index 90097703..bc7119b1 100644 --- a/tests/stats.test.ts +++ b/tests/stats.test.ts @@ -1,4 +1,4 @@ -import { cycleMigrations } from '@hirosystems/api-toolkit'; +import { runMigrations } from '@hirosystems/api-toolkit'; import { buildApiServer } from '../src/api/init'; import { MIGRATIONS_DIR, PgStore } from '../src/pg/pg-store'; import { TestChainhookPayloadBuilder, TestFastifyServer, randomHash } from './helpers'; @@ -8,14 +8,15 @@ describe('/stats', () => { let fastify: TestFastifyServer; beforeEach(async () => { + await runMigrations(MIGRATIONS_DIR, 'up'); db = await PgStore.connect({ skipMigrations: true }); fastify = await buildApiServer({ db }); - await cycleMigrations(MIGRATIONS_DIR); }); afterEach(async () => { await fastify.close(); await db.close(); + await runMigrations(MIGRATIONS_DIR, 'down'); }); describe('/stats/inscriptions', () => { @@ -64,34 +65,11 @@ describe('/stats', () => { }; test('returns stats when processing blocks in order', async () => { - await db.updateInscriptions(testRevealApply(778_000)); - await db.updateInscriptions(testRevealApply(778_000)); - await db.updateInscriptions(testRevealApply(778_001)); - await db.updateInscriptions(testRevealApply(778_002)); - await db.updateInscriptions(testRevealApply(778_005)); - await db.updateInscriptions(testRevealApply(778_005)); - await db.updateInscriptions(testRevealApply(778_010)); - await db.updateInscriptions(testRevealApply(778_010)); - await db.updateInscriptions(testRevealApply(778_010)); - - const response = await fastify.inject({ - method: 'GET', - url: '/ordinals/v1/stats/inscriptions', - }); - expect(response.statusCode).toBe(200); - expect(response.json()).toStrictEqual(EXPECTED); - }); - - test('returns stats when processing blocks out-of-order', async () => { - await db.updateInscriptions(testRevealApply(778_001)); - await db.updateInscriptions(testRevealApply(778_010)); - await db.updateInscriptions(testRevealApply(778_000)); - await db.updateInscriptions(testRevealApply(778_000)); - await db.updateInscriptions(testRevealApply(778_005)); - await db.updateInscriptions(testRevealApply(778_010)); - await db.updateInscriptions(testRevealApply(778_002)); - await db.updateInscriptions(testRevealApply(778_010)); - await db.updateInscriptions(testRevealApply(778_005)); + await db.updateInscriptions(testRevealApply(778_000, [0, 1])); + await db.updateInscriptions(testRevealApply(778_001, [2])); + await db.updateInscriptions(testRevealApply(778_002, [3])); + await db.updateInscriptions(testRevealApply(778_005, [4, 5])); + await db.updateInscriptions(testRevealApply(778_010, [6, 7, 8])); const response = await fastify.inject({ method: 'GET', @@ -102,20 +80,16 @@ describe('/stats', () => { }); test('returns stats when processing rollbacks', async () => { - const payloadApply = testRevealApply(778_004); + const payloadApply = testRevealApply(778_004, [4]); const payloadRollback = { ...payloadApply, apply: [], rollback: payloadApply.apply }; - await db.updateInscriptions(testRevealApply(778_001)); - await db.updateInscriptions(testRevealApply(778_010)); - await db.updateInscriptions(testRevealApply(778_000)); + await db.updateInscriptions(testRevealApply(778_000, [0, 1])); + await db.updateInscriptions(testRevealApply(778_001, [2])); + await db.updateInscriptions(testRevealApply(778_002, [3])); await db.updateInscriptions(payloadApply); - await db.updateInscriptions(testRevealApply(778_005)); - await db.updateInscriptions(testRevealApply(778_000)); await db.updateInscriptions(payloadRollback); - await db.updateInscriptions(testRevealApply(778_010)); - await db.updateInscriptions(testRevealApply(778_002)); - await db.updateInscriptions(testRevealApply(778_010)); - await db.updateInscriptions(testRevealApply(778_005)); + await db.updateInscriptions(testRevealApply(778_005, [4, 5])); + await db.updateInscriptions(testRevealApply(778_010, [6, 7, 8])); const response = await fastify.inject({ method: 'GET', @@ -127,12 +101,11 @@ describe('/stats', () => { }); test('range filters', async () => { - await db.updateInscriptions(testRevealApply(778_000)); - await db.updateInscriptions(testRevealApply(778_001)); - await db.updateInscriptions(testRevealApply(778_002)); - await db.updateInscriptions(testRevealApply(778_005)); - await db.updateInscriptions(testRevealApply(778_005)); - await db.updateInscriptions(testRevealApply(778_010)); + await db.updateInscriptions(testRevealApply(778_000, [0])); + await db.updateInscriptions(testRevealApply(778_001, [1])); + await db.updateInscriptions(testRevealApply(778_002, [2])); + await db.updateInscriptions(testRevealApply(778_005, [3, 4])); + await db.updateInscriptions(testRevealApply(778_010, [5])); const responseFrom = await fastify.inject({ method: 'GET', @@ -222,34 +195,35 @@ describe('/stats', () => { }); }); -function testRevealApply(blockHeight: number) { - const randomHex = randomHash(); - return new TestChainhookPayloadBuilder() - .apply() - .block({ - height: blockHeight, - hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', - timestamp: 1676913207, - }) - .transaction({ - hash: `0x${randomHex}`, - }) - .inscriptionRevealed({ - content_bytes: '0x48656C6C6F', - content_type: 'image/png', - content_length: 5, - inscription_number: Math.floor(Math.random() * 100000), - inscription_fee: 2805, - inscription_id: `${randomHex}i0`, - inscription_output_value: 10000, - inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', - ordinal_number: Math.floor(Math.random() * 1000000), - ordinal_block_height: Math.floor(Math.random() * 777000), - ordinal_offset: 0, - satpoint_post_inscription: `${randomHex}:0:0`, - inscription_input_index: 0, - transfers_pre_inscription: 0, - tx_index: 0, - }) - .build(); +function testRevealApply(blockHeight: number, numbers: number[]) { + const block = new TestChainhookPayloadBuilder().apply().block({ + height: blockHeight, + hash: '0x00000000000000000002a90330a99f67e3f01eb2ce070b45930581e82fb7a91d', + timestamp: 1676913207, + }); + for (const number of numbers) { + const randomHex = randomHash(); + block + .transaction({ + hash: `0x${randomHex}`, + }) + .inscriptionRevealed({ + content_bytes: '0x48656C6C6F', + content_type: 'image/png', + content_length: 5, + inscription_number: number, + inscription_fee: 2805, + inscription_id: `${randomHex}i0`, + inscription_output_value: 10000, + inscriber_address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', + ordinal_number: Math.floor(Math.random() * 1000000), + ordinal_block_height: Math.floor(Math.random() * 777000), + ordinal_offset: 0, + satpoint_post_inscription: `${randomHex}:0:0`, + inscription_input_index: 0, + transfers_pre_inscription: 0, + tx_index: 0, + }); + } + return block.build(); } diff --git a/tests/status.test.ts b/tests/status.test.ts index 87b845be..50596a48 100644 --- a/tests/status.test.ts +++ b/tests/status.test.ts @@ -1,4 +1,4 @@ -import { cycleMigrations } from '@hirosystems/api-toolkit'; +import { runMigrations } from '@hirosystems/api-toolkit'; import { buildApiServer } from '../src/api/init'; import { MIGRATIONS_DIR, PgStore } from '../src/pg/pg-store'; import { TestChainhookPayloadBuilder, TestFastifyServer } from './helpers'; @@ -8,14 +8,15 @@ describe('Status', () => { let fastify: TestFastifyServer; beforeEach(async () => { + await runMigrations(MIGRATIONS_DIR, 'up'); db = await PgStore.connect({ skipMigrations: true }); fastify = await buildApiServer({ db }); - await cycleMigrations(MIGRATIONS_DIR); }); afterEach(async () => { await fastify.close(); await db.close(); + await runMigrations(MIGRATIONS_DIR, 'down'); }); test('returns status when db is empty', async () => { @@ -41,7 +42,7 @@ describe('Status', () => { content_bytes: '0x48656C6C6F', content_type: 'text/plain;charset=utf-8', content_length: 5, - inscription_number: 7, + inscription_number: 0, inscription_fee: 2805, inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', inscription_output_value: 10000, @@ -90,7 +91,7 @@ describe('Status', () => { server_version: 'ordinals-api v0.0.1 (test:123456)', status: 'ready', block_height: 791975, - max_inscription_number: 7, + max_inscription_number: 0, max_cursed_inscription_number: -2, }); });