diff --git a/api/ordinals/src/api/schemas.ts b/api/ordinals/src/api/schemas.ts index 232f244..76a9632 100644 --- a/api/ordinals/src/api/schemas.ts +++ b/api/ordinals/src/api/schemas.ts @@ -342,6 +342,7 @@ export const InscriptionResponse = Type.Object( examples: ['brc20'], }) ), + charms: Type.Array(Type.String({ examples: ['coin', 'palindrome', 'vindicated'] })), }, { title: 'Inscription Response' } ); diff --git a/api/ordinals/src/api/util/helpers.ts b/api/ordinals/src/api/util/helpers.ts index f5643fa..77e63fe 100644 --- a/api/ordinals/src/api/util/helpers.ts +++ b/api/ordinals/src/api/util/helpers.ts @@ -31,6 +31,37 @@ function parseTimestamp(timestamp: number): number { return timestamp * 1000; } +enum Charm { + coin = 0, + cursed = 1, + epic = 2, + legendary = 3, + lost = 4, + nineball = 5, + rare = 6, + reinscription = 7, + unbound = 8, + uncommon = 9, + vindicated = 10, + mythic = 11, + burned = 12, + palindrome = 13, +} + +function parseCharms(charms: string): string[] { + const charmsVal = parseInt(charms); + const result: Charm[] = []; + for (const charm in Charm) { + if (!isNaN(Number(charm))) { + const charmValue = Number(charm); + if (charmsVal & (1 << charmValue)) { + result.push(charmValue as Charm); + } + } + } + return result.map(charm => Charm[charm]); +} + export function parseDbInscriptions( items: DbFullyLocatedInscriptionResult[] ): InscriptionResponseType[] { @@ -63,6 +94,7 @@ export function parseDbInscriptions( metadata: i.metadata ? JSON.parse(i.metadata) : null, delegate: i.delegate ?? null, meta_protocol: i.metaprotocol ?? null, + charms: parseCharms(i.charms), })); } export function parseDbInscription(item: DbFullyLocatedInscriptionResult): InscriptionResponseType { diff --git a/api/ordinals/src/pg/pg-store.ts b/api/ordinals/src/pg/pg-store.ts index 5f09594..f2528c0 100644 --- a/api/ordinals/src/pg/pg-store.ts +++ b/api/ordinals/src/pg/pg-store.ts @@ -190,6 +190,7 @@ export class PgStore extends BasePgStore { i.tx_index AS genesis_tx_index, i.timestamp AS genesis_timestamp, i.address AS genesis_address, + i.charms, cur.address, cur.tx_index, cur.block_height, diff --git a/api/ordinals/src/pg/types.ts b/api/ordinals/src/pg/types.ts index 4f39d0f..5d0f8e5 100644 --- a/api/ordinals/src/pg/types.ts +++ b/api/ordinals/src/pg/types.ts @@ -37,6 +37,7 @@ export type DbFullyLocatedInscriptionResult = { pointer: number | null; metaprotocol: string | null; delegate: string | null; + charms: string; }; export type DbLocation = { diff --git a/api/ordinals/tests/api/cache.test.ts b/api/ordinals/tests/api/cache.test.ts index e409ea0..279a8b0 100644 --- a/api/ordinals/tests/api/cache.test.ts +++ b/api/ordinals/tests/api/cache.test.ts @@ -65,6 +65,7 @@ describe('ETag cache', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '9000', + charms: 0, }); const response = await fastify.inject({ method: 'GET', @@ -165,6 +166,7 @@ describe('ETag cache', () => { prev_offset: null, transfer_type: 'transferred', rarity: 'common', + charms: 0, }); await inscriptionReveal(db.sql, { content: '0x48656C6C6F', @@ -197,6 +199,7 @@ describe('ETag cache', () => { prev_offset: null, transfer_type: 'transferred', rarity: 'common', + charms: 0, }); // ETag response @@ -277,6 +280,7 @@ describe('ETag cache', () => { prev_offset: null, transfer_type: 'transferred', rarity: 'common', + charms: 0, }); // ETag response @@ -328,6 +332,7 @@ describe('ETag cache', () => { prev_offset: null, transfer_type: 'transferred', rarity: 'common', + charms: 0, }); // Cache busted @@ -371,6 +376,7 @@ describe('ETag cache', () => { prev_offset: null, transfer_type: 'transferred', rarity: 'common', + charms: 0, }); // ETag response @@ -422,6 +428,7 @@ describe('ETag cache', () => { prev_offset: null, transfer_type: 'transferred', rarity: 'common', + charms: 0, }); // Cache busted diff --git a/api/ordinals/tests/api/inscriptions.test.ts b/api/ordinals/tests/api/inscriptions.test.ts index edb8273..4c4b24b 100644 --- a/api/ordinals/tests/api/inscriptions.test.ts +++ b/api/ordinals/tests/api/inscriptions.test.ts @@ -68,6 +68,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '51483', + charms: 10369, }); const expected = { address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', @@ -98,6 +99,7 @@ describe('/inscriptions', () => { metadata: null, meta_protocol: null, delegate: null, + charms: ['coin', 'reinscription', 'mythic', 'palindrome'], }; // By inscription id @@ -154,6 +156,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '51483', + charms: 0, }); await insertTestInscriptionRecursion(db.sql, { inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', @@ -200,6 +203,7 @@ describe('/inscriptions', () => { metadata: null, meta_protocol: null, delegate: null, + charms: [], }; // By inscription id @@ -251,6 +255,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: 'f351d86c6e6cae3c64e297e7463095732f216875bcc1f3c03f950a492bb25421i0', @@ -283,6 +288,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '51483', + charms: 0, }); await insertTestInscriptionParent(db.sql, { inscription_id: 'f351d86c6e6cae3c64e297e7463095732f216875bcc1f3c03f950a492bb25421i0', @@ -330,6 +336,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); const response = await fastify.inject({ method: 'GET', @@ -371,6 +378,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '51483', + charms: 0, }); const expected = { address: '', @@ -401,6 +409,7 @@ describe('/inscriptions', () => { metadata: null, meta_protocol: null, delegate: null, + charms: [], }; // By inscription id @@ -452,6 +461,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '51483', + charms: 0, }); const expected = { address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', @@ -482,6 +492,7 @@ describe('/inscriptions', () => { metadata: null, meta_protocol: null, delegate: null, + charms: [], }; // By inscription id @@ -533,6 +544,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '51483', + charms: 0, }); // Transfer 1 @@ -590,6 +602,7 @@ describe('/inscriptions', () => { metadata: null, meta_protocol: null, delegate: null, + charms: [], }); // Transfer 2 @@ -647,6 +660,7 @@ describe('/inscriptions', () => { metadata: null, meta_protocol: null, delegate: null, + charms: [], }); }); @@ -682,6 +696,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '51483', + charms: 0, }); // Multiple transfers @@ -759,6 +774,7 @@ describe('/inscriptions', () => { metadata: null, meta_protocol: null, delegate: null, + charms: [], }); }); @@ -794,6 +810,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '51483', + charms: 0, }); // Transfer 1 @@ -851,6 +868,7 @@ describe('/inscriptions', () => { metadata: null, meta_protocol: null, delegate: null, + charms: [], }); // Transfer 2 @@ -908,6 +926,7 @@ describe('/inscriptions', () => { metadata: null, meta_protocol: null, delegate: null, + charms: [], }); }); @@ -943,6 +962,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '51483', + charms: 0, }); const response = await fastify.inject({ method: 'GET', @@ -985,6 +1005,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '51483', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: '42174ecc8a245841035793390bb53d63b3c2acb61366446f601b09e73b94b656i0', @@ -1017,6 +1038,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '51483', + charms: 0, }); const response = await fastify.inject({ method: 'GET', @@ -1061,6 +1083,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); // Transfer 1 @@ -1213,6 +1236,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: '7ac73ecd01b9da4a7eab904655416dbfe8e03f193e091761b5a63ad0963570cdi0', @@ -1245,6 +1269,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); // No transfers on this block because they are all genesis. @@ -1573,6 +1598,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', @@ -1605,6 +1631,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); const response1 = await fastify.inject({ @@ -1644,6 +1671,7 @@ describe('/inscriptions', () => { metadata: null, meta_protocol: null, delegate: null, + charms: [], }, { address: 'bc1pscktlmn99gyzlvymvrezh6vwd0l4kg06tg5rvssw0czg8873gz5sdkteqj', @@ -1674,6 +1702,7 @@ describe('/inscriptions', () => { metadata: null, meta_protocol: null, delegate: null, + charms: [], }, ]); }); @@ -1711,6 +1740,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', @@ -1743,6 +1773,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); const response1 = await fastify.inject({ @@ -1782,6 +1813,7 @@ describe('/inscriptions', () => { metadata: null, meta_protocol: null, delegate: null, + charms: [], }; expect(responseJson1.results[0]).toStrictEqual(result1); @@ -1822,6 +1854,7 @@ describe('/inscriptions', () => { metadata: null, meta_protocol: null, delegate: null, + charms: [], }; expect(responseJson2.results[0]).toStrictEqual(result2); @@ -1869,6 +1902,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', @@ -1901,6 +1935,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'mythic', coinbase_height: '0', + charms: 0, }); const response1 = await fastify.inject({ @@ -1963,6 +1998,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', @@ -1995,6 +2031,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); const response1 = await fastify.inject({ @@ -2060,6 +2097,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', @@ -2092,6 +2130,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); const response1 = await fastify.inject({ @@ -2153,6 +2192,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', @@ -2185,6 +2225,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); const response1 = await fastify.inject({ @@ -2248,6 +2289,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); const response4 = await fastify.inject({ method: 'GET', @@ -2293,6 +2335,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', @@ -2325,6 +2368,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); const response1 = await fastify.inject({ @@ -2372,6 +2416,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', @@ -2404,6 +2449,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); const response2 = await fastify.inject({ @@ -2459,6 +2505,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', @@ -2491,6 +2538,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); const response2 = await fastify.inject({ @@ -2546,6 +2594,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '51483', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', @@ -2578,6 +2627,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '200', + charms: 0, }); const response2 = await fastify.inject({ @@ -2633,6 +2683,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', @@ -2665,6 +2716,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); const response2 = await fastify.inject({ @@ -2719,6 +2771,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', @@ -2751,6 +2804,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); const response1 = await fastify.inject({ @@ -2797,6 +2851,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', @@ -2829,6 +2884,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); const response1 = await fastify.inject({ @@ -2885,6 +2941,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', @@ -2919,6 +2976,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); await insertTestInscriptionRecursion(db.sql, { inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', @@ -2982,6 +3040,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', @@ -3014,6 +3073,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); const response1 = await fastify.inject({ @@ -3073,6 +3133,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', @@ -3105,6 +3166,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); const response1 = await fastify.inject({ @@ -3163,6 +3225,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', @@ -3195,6 +3258,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1i0', @@ -3227,6 +3291,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '0', + charms: 0, }); const response1 = await fastify.inject({ @@ -3284,6 +3349,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', @@ -3316,6 +3382,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'epic', coinbase_height: '650000', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1i0', @@ -3348,6 +3415,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'mythic', coinbase_height: '0', + charms: 0, }); const response1 = await fastify.inject({ @@ -3405,6 +3473,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', @@ -3437,6 +3506,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1i0', @@ -3469,6 +3539,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '0', + charms: 0, }); const response1 = await fastify.inject({ @@ -3526,6 +3597,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0', @@ -3558,6 +3630,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: '567c7605439dfdc3a289d13fd2132237852f4a56e784b9364ba94499d5f9baf1i0', @@ -3590,6 +3663,7 @@ describe('/inscriptions', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '0', + charms: 0, }); const response1 = await fastify.inject({ diff --git a/api/ordinals/tests/api/sats.test.ts b/api/ordinals/tests/api/sats.test.ts index 2609c81..ee96995 100644 --- a/api/ordinals/tests/api/sats.test.ts +++ b/api/ordinals/tests/api/sats.test.ts @@ -84,6 +84,7 @@ describe('/sats', () => { prev_offset: null, transfer_type: 'transferred', rarity: 'common', + charms: 0, }); const response = await fastify.inject({ method: 'GET', @@ -127,6 +128,7 @@ describe('/sats', () => { prev_offset: null, transfer_type: 'transferred', rarity: 'common', + charms: 0, }); await inscriptionReveal(db.sql, { content: '0x48656C6C6F', @@ -160,6 +162,7 @@ describe('/sats', () => { prev_offset: null, transfer_type: 'transferred', rarity: 'common', + charms: 0, }); // Simulate the inscription transfer for -7 await inscriptionTransfer(db.sql, { @@ -219,6 +222,7 @@ describe('/sats', () => { metadata: null, meta_protocol: null, delegate: null, + charms: [], }, { address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td', @@ -250,6 +254,7 @@ describe('/sats', () => { metadata: null, meta_protocol: null, delegate: null, + charms: [], }, ]); diff --git a/api/ordinals/tests/api/status.test.ts b/api/ordinals/tests/api/status.test.ts index 968b022..0c26f53 100644 --- a/api/ordinals/tests/api/status.test.ts +++ b/api/ordinals/tests/api/status.test.ts @@ -77,6 +77,7 @@ describe('Status', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); await inscriptionReveal(db.sql, { inscription_id: 'a98d7055a77fa0b96cc31e30bb8bacf777382d1b67f1b7eca6f2014e961591c8i0', @@ -109,6 +110,7 @@ describe('Status', () => { transfer_type: 'transferred', rarity: 'common', coinbase_height: '650000', + charms: 0, }); await updateTestChainTip(db.sql, 791975); diff --git a/api/ordinals/tests/helpers.ts b/api/ordinals/tests/helpers.ts index c7594ee..15c7c13 100644 --- a/api/ordinals/tests/helpers.ts +++ b/api/ordinals/tests/helpers.ts @@ -77,6 +77,7 @@ type TestOrdinalsInscriptionsRow = { metaprotocol: string | null; delegate: string | null; timestamp: number; + charms: number; }; async function insertTestInscription(sql: PgSqlClient, row: TestOrdinalsInscriptionsRow) { await sql`INSERT INTO inscriptions ${sql(row)}`; @@ -230,6 +231,7 @@ export async function inscriptionReveal(sql: PgSqlClient, reveal: TestOrdinalsIn metaprotocol: reveal.metaprotocol, delegate: reveal.delegate, timestamp: reveal.timestamp, + charms: reveal.charms, }); await insertTestLocation(sql, { ordinal_number: reveal.ordinal_number, diff --git a/components/chainhook-types-rs/src/ordinals.rs b/components/chainhook-types-rs/src/ordinals.rs index 71aa7db..9cab2d9 100644 --- a/components/chainhook-types-rs/src/ordinals.rs +++ b/components/chainhook-types-rs/src/ordinals.rs @@ -39,45 +39,6 @@ pub enum OrdinalInscriptionCurseType { Generic, } -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] -pub struct OrdinalInscriptionCharms { - pub coin: bool, - pub cursed: bool, - pub epic: bool, - pub legendary: bool, - pub lost: bool, - pub nineball: bool, - pub rare: bool, - pub reinscription: bool, - pub unbound: bool, - pub uncommon: bool, - pub vindicated: bool, - pub mythic: bool, - pub burned: bool, - pub palindrome: bool, -} - -impl OrdinalInscriptionCharms { - pub fn none() -> Self { - OrdinalInscriptionCharms { - coin: false, - cursed: false, - epic: false, - legendary: false, - lost: false, - nineball: false, - rare: false, - reinscription: false, - unbound: false, - uncommon: false, - vindicated: false, - mythic: false, - burned: false, - palindrome: false, - } - } -} - #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct OrdinalInscriptionRevealData { pub content_bytes: String, @@ -101,7 +62,7 @@ pub struct OrdinalInscriptionRevealData { pub transfers_pre_inscription: u32, pub satpoint_post_inscription: String, pub curse_type: Option, - pub charms: OrdinalInscriptionCharms, + pub charms: u16, } impl OrdinalInscriptionNumber { diff --git a/components/ordhook-core/src/core/meta_protocols/brc20/test_utils.rs b/components/ordhook-core/src/core/meta_protocols/brc20/test_utils.rs index b3daaa4..8093fe3 100644 --- a/components/ordhook-core/src/core/meta_protocols/brc20/test_utils.rs +++ b/components/ordhook-core/src/core/meta_protocols/brc20/test_utils.rs @@ -1,7 +1,7 @@ use chainhook_sdk::utils::Context; use chainhook_types::{ - OrdinalInscriptionCharms, OrdinalInscriptionNumber, OrdinalInscriptionRevealData, - OrdinalInscriptionTransferData, OrdinalInscriptionTransferDestination, + OrdinalInscriptionNumber, OrdinalInscriptionRevealData, OrdinalInscriptionTransferData, + OrdinalInscriptionTransferDestination, }; pub fn get_test_ctx() -> Context { @@ -88,7 +88,7 @@ impl Brc20RevealBuilder { satpoint_post_inscription: "9bb2314d666ae0b1db8161cb373fcc1381681f71445c4e0335aa80ea9c37fcdd:0:0".to_string(), curse_type: None, - charms: OrdinalInscriptionCharms::none(), + charms: 0, } } } diff --git a/components/ordhook-core/src/core/pipeline/processors/inscription_indexing.rs b/components/ordhook-core/src/core/pipeline/processors/inscription_indexing.rs index 773cfcb..73b794d 100644 --- a/components/ordhook-core/src/core/pipeline/processors/inscription_indexing.rs +++ b/components/ordhook-core/src/core/pipeline/processors/inscription_indexing.rs @@ -25,7 +25,7 @@ use crate::{ protocol::{ inscription_parsing::parse_inscriptions_in_standardized_block, inscription_sequencing::{ - augment_block_with_inscriptions, get_bitcoin_network, get_jubilee_block_height, + update_block_inscriptions_with_consensus_sequence_data, get_bitcoin_network, get_jubilee_block_height, parallelize_inscription_data_computations, }, satoshi_numbering::TraversalResult, @@ -224,7 +224,7 @@ pub async fn index_block( ctx, )?; if has_inscription_reveals { - augment_block_with_inscriptions(block, sequence_cursor, cache_l1, &ord_tx, ctx) + update_block_inscriptions_with_consensus_sequence_data(block, sequence_cursor, cache_l1, &ord_tx, ctx) .await?; } augment_block_with_transfers(block, &ord_tx, ctx).await?; diff --git a/components/ordhook-core/src/core/protocol/inscription_parsing.rs b/components/ordhook-core/src/core/protocol/inscription_parsing.rs index 95ac14d..e3c27e7 100644 --- a/components/ordhook-core/src/core/protocol/inscription_parsing.rs +++ b/components/ordhook-core/src/core/protocol/inscription_parsing.rs @@ -3,8 +3,8 @@ use bitcoin::Witness; use chainhook_sdk::utils::Context; use chainhook_types::{ BitcoinBlockData, BitcoinNetwork, BitcoinTransactionData, BlockIdentifier, - OrdinalInscriptionCharms, OrdinalInscriptionCurseType, OrdinalInscriptionNumber, - OrdinalInscriptionRevealData, OrdinalInscriptionTransferData, OrdinalOperation, + OrdinalInscriptionCurseType, OrdinalInscriptionNumber, OrdinalInscriptionRevealData, + OrdinalOperation, }; use serde_json::json; use std::collections::HashMap; @@ -102,7 +102,7 @@ pub fn parse_inscriptions_from_witness( transfers_pre_inscription: 0, satpoint_post_inscription: format!(""), curse_type, - charms: OrdinalInscriptionCharms::none(), + charms: 0, }; inscriptions.push((reveal_data, envelope.payload)); } @@ -169,90 +169,19 @@ pub fn parse_inscriptions_in_standardized_block( } } -pub fn get_inscriptions_revealed_in_block( - block: &BitcoinBlockData, -) -> Vec<&OrdinalInscriptionRevealData> { - let mut ops = vec![]; - for tx in block.transactions.iter() { - for op in tx.metadata.ordinal_operations.iter() { - if let OrdinalOperation::InscriptionRevealed(op) = op { - ops.push(op); - } - } - } - ops -} - -pub fn get_inscriptions_transferred_in_block( - block: &BitcoinBlockData, -) -> Vec<&OrdinalInscriptionTransferData> { - let mut ops = vec![]; - for tx in block.transactions.iter() { - for op in tx.metadata.ordinal_operations.iter() { - if let OrdinalOperation::InscriptionTransferred(op) = op { - ops.push(op); - } - } - } - ops -} - #[cfg(test)] mod test { use std::collections::HashMap; use chainhook_sdk::utils::Context; - use chainhook_types::{ - BitcoinBlockData, BitcoinTransactionData, OrdinalInscriptionTransferData, - OrdinalInscriptionTransferDestination, OrdinalOperation, - }; - use test_case::test_case; + use chainhook_types::OrdinalOperation; use crate::{ config::Config, core::test_builders::{TestBlockBuilder, TestTransactionBuilder, TestTxInBuilder}, }; - use super::{ - get_inscriptions_revealed_in_block, get_inscriptions_transferred_in_block, - parse_inscriptions_in_standardized_block, - }; - - pub fn new_test_transfer_tx_with_operation() -> BitcoinTransactionData { - TestTransactionBuilder::new() - .ordinal_operations(vec![OrdinalOperation::InscriptionTransferred( - OrdinalInscriptionTransferData { - ordinal_number: 300144140535834, - destination: OrdinalInscriptionTransferDestination::Transferred( - "bc1pcwway0ne322s0lrc5e905f3chuclvnyy3z6wn86azkgmgcprf3tqvyy7ws" - .to_string(), - ), - satpoint_pre_transfer: - "ab2683db34e335c89a5c1d634e6c5bd8d8bca8ded281be84f71f921c9e8783b2:0:0" - .to_string(), - satpoint_post_transfer: - "42fa098abab8d5cca1c303a97bd0404cf8e9b8faaab6dd228a309e66daff8fae:1:0" - .to_string(), - post_transfer_output_value: Some(546), - tx_index: 54, - }, - )]) - .build() - } - - #[test_case(&TestBlockBuilder::new().build() => 0; "with empty block")] - #[test_case(&TestBlockBuilder::new().transactions(vec![TestTransactionBuilder::new_with_operation().build()]).build() => 1; "with reveal transaction")] - #[test_case(&TestBlockBuilder::new().transactions(vec![new_test_transfer_tx_with_operation()]).build() => 0; "with transfer transaction")] - fn gets_reveals_in_block(block: &BitcoinBlockData) -> usize { - get_inscriptions_revealed_in_block(block).len() - } - - #[test_case(&TestBlockBuilder::new().build() => 0; "with empty block")] - #[test_case(&TestBlockBuilder::new().transactions(vec![TestTransactionBuilder::new_with_operation().build()]).build() => 0; "with reveal transaction")] - #[test_case(&TestBlockBuilder::new().transactions(vec![new_test_transfer_tx_with_operation()]).build() => 1; "with transfer transaction")] - fn gets_transfers_in_block(block: &BitcoinBlockData) -> usize { - get_inscriptions_transferred_in_block(block).len() - } + use super::parse_inscriptions_in_standardized_block; #[test] fn parses_inscriptions_in_block() { diff --git a/components/ordhook-core/src/core/protocol/inscription_sequencing.rs b/components/ordhook-core/src/core/protocol/inscription_sequencing.rs index 7391177..e284577 100644 --- a/components/ordhook-core/src/core/protocol/inscription_sequencing.rs +++ b/components/ordhook-core/src/core/protocol/inscription_sequencing.rs @@ -23,7 +23,7 @@ use crate::{ try_debug, try_error, try_info, utils::format_inscription_id, }; -use ord::height::Height; +use ord::{charm::Charm, height::Height, sat::Sat}; use std::sync::mpsc::channel; @@ -391,26 +391,25 @@ pub fn get_bitcoin_network(network: &BitcoinNetwork) -> Network { /// Given a `BitcoinBlockData` that have been augmented with the functions `parse_inscriptions_in_raw_tx`, /// `parse_inscriptions_in_standardized_tx` or `parse_inscriptions_and_standardize_block`, mutate the ordinals drafted /// informations with actual, consensus data. -pub async fn augment_block_with_inscriptions( +pub async fn update_block_inscriptions_with_consensus_sequence_data( block: &mut BitcoinBlockData, sequence_cursor: &mut SequenceCursor, inscriptions_data: &mut BTreeMap<(TransactionIdentifier, usize, u64), TraversalResult>, db_tx: &Transaction<'_>, ctx: &Context, ) -> Result<(), String> { - // Handle re-inscriptions + // Check if we've previously inscribed over any satoshi being inscribed to in this new block. This would be a reinscription. let mut reinscriptions_data = ordinals_pg::get_reinscriptions_for_block(inscriptions_data, db_tx).await?; - // Handle sat oveflows - let mut sats_overflows = VecDeque::new(); - + // Keep a reference of inscribed satoshis that fall outside of this block's total sats. These would be unbound inscriptions. + let mut sat_overflows = VecDeque::new(); let network = get_bitcoin_network(&block.metadata.network); let coinbase_subsidy = Height(block.block_identifier.index as u32).subsidy(); let coinbase_tx = &block.transactions[0].clone(); let mut cumulated_fees = 0u64; for (tx_index, tx) in block.transactions.iter_mut().enumerate() { - augment_transaction_with_ordinals_inscriptions_data( + update_tx_inscriptions_with_consensus_sequence_data( tx, tx_index, &block.block_identifier, @@ -420,7 +419,7 @@ pub async fn augment_block_with_inscriptions( coinbase_tx, coinbase_subsidy, &mut cumulated_fees, - &mut sats_overflows, + &mut sat_overflows, &mut reinscriptions_data, db_tx, ctx, @@ -428,8 +427,7 @@ pub async fn augment_block_with_inscriptions( .await?; } - // Handle sats overflow - while let Some((tx_index, op_index)) = sats_overflows.pop_front() { + while let Some((tx_index, op_index)) = sat_overflows.pop_front() { let OrdinalOperation::InscriptionRevealed(ref mut inscription_data) = block.transactions[tx_index].metadata.ordinal_operations[op_index] else { @@ -455,13 +453,11 @@ pub async fn augment_block_with_inscriptions( Ok(()) } -/// Given a `BitcoinTransactionData` that have been augmented with the functions `parse_inscriptions_in_raw_tx` or -/// `parse_inscriptions_in_standardized_tx`, mutate the ordinals drafted informations with actual, consensus data, by -/// using informations from `inscription_data` and `reinscription_data`. +/// Given a `BitcoinTransactionData` that have been augmented with `parse_inscriptions_in_standardized_tx`, mutate the ordinals +/// drafted informations with actual, consensus data, by using informations from `inscription_data` and `reinscription_data`. /// -/// Transactions are not fully correct from a consensus point of view state transient state after the execution of this -/// function. -async fn augment_transaction_with_ordinals_inscriptions_data( +/// Transactions are not fully correct from a consensus point of view state transient state after the execution of this function. +async fn update_tx_inscriptions_with_consensus_sequence_data( tx: &mut BitcoinTransactionData, tx_index: usize, block_identifier: &BlockIdentifier, @@ -476,18 +472,21 @@ async fn augment_transaction_with_ordinals_inscriptions_data( db_tx: &Transaction<'_>, ctx: &Context, ) -> Result { - let inputs = tx + if tx.metadata.ordinal_operations.is_empty() { + return Ok(false); + } + + let tx_input_values = tx .metadata .inputs .iter() .map(|i| i.previous_output.value) .collect::>(); + let mut mut_operations = vec![]; + mut_operations.append(&mut tx.metadata.ordinal_operations); - let any_event = tx.metadata.ordinal_operations.is_empty() == false; - let mut mutated_operations = vec![]; - mutated_operations.append(&mut tx.metadata.ordinal_operations); let mut inscription_subindex = 0; - for (op_index, op) in mutated_operations.iter_mut().enumerate() { + for (op_index, op) in mut_operations.iter_mut().enumerate() { let (mut is_cursed, inscription) = match op { OrdinalOperation::InscriptionRevealed(inscription) => { (inscription.curse_type.as_ref().is_some(), inscription) @@ -496,7 +495,7 @@ async fn augment_transaction_with_ordinals_inscriptions_data( }; let (input_index, relative_offset) = match inscription.inscription_pointer { - Some(pointer) => resolve_absolute_pointer(&inputs, pointer), + Some(pointer) => resolve_absolute_pointer(&tx_input_values, pointer), None => (inscription.inscription_input_index, 0), }; @@ -506,38 +505,35 @@ async fn augment_transaction_with_ordinals_inscriptions_data( match inscriptions_data.get(&(transaction_identifier, input_index, relative_offset)) { Some(traversal) => traversal, None => { - let err_msg = format!( - "Unable to retrieve backward traversal result for inscription {}", + return Err(format!( + "Unable to retrieve backward traversal result for inscription in tx {}", tx.transaction_identifier.hash - ); - try_error!(ctx, "{}", err_msg); - std::process::exit(1); + )); } }; - // Do we need to curse the inscription? + // Do we need to curse the inscription? Is this inscription re-inscribing an existing blessed inscription? let mut inscription_number = sequence_cursor .pick_next(is_cursed, block_identifier.index, network, db_tx) .await?; let mut curse_type_override = None; if !is_cursed { - // Is this inscription re-inscribing an existing blessed inscription? if let Some(exisiting_inscription_id) = reinscriptions_data.get(&traversal.ordinal_number) { try_info!( ctx, - "Satoshi #{} was inscribed with blessed inscription {}, cursing inscription {}", + "Satoshi {} was previously inscribed with blessed inscription {}, cursing inscription {}", traversal.ordinal_number, exisiting_inscription_id, traversal.get_inscription_id(), ); - is_cursed = true; inscription_number = sequence_cursor .pick_next(is_cursed, block_identifier.index, network, db_tx) .await?; - curse_type_override = Some(OrdinalInscriptionCurseType::Reinscription) + curse_type_override = Some(OrdinalInscriptionCurseType::Reinscription); + Charm::Reinscription.set(&mut inscription.charms); } }; @@ -564,8 +560,6 @@ async fn augment_transaction_with_ordinals_inscriptions_data( cumulated_fees, ctx, ); - - // Compute satpoint_post_inscription inscription.satpoint_post_inscription = satpoint_post_transfer; inscription_subindex += 1; @@ -579,17 +573,30 @@ async fn augment_transaction_with_ordinals_inscriptions_data( // spent to fees are numbered as if they appear last in the block in which they // are revealed. sats_overflows.push_back((tx_index, op_index)); + Charm::Unbound.set(&mut inscription.charms); continue; } - OrdinalInscriptionTransferDestination::Burnt(_) => {} + OrdinalInscriptionTransferDestination::Burnt(_) => { + Charm::Burned.set(&mut inscription.charms); + } OrdinalInscriptionTransferDestination::Transferred(address) => { inscription.inscription_output_value = output_value.unwrap_or(0); inscription.inscriber_address = Some(address); + if output_value.is_none() { + Charm::Lost.set(&mut inscription.charms); + } } }; - // The reinscriptions_data needs to be augmented as we go, to handle transaction chaining. - if !is_cursed { + inscription.charms |= Sat(traversal.ordinal_number).charms(); + if is_cursed { + if block_identifier.index >= get_jubilee_block_height(network) { + Charm::Vindicated.set(&mut inscription.charms); + } else { + Charm::Cursed.set(&mut inscription.charms); + } + } else { + // The reinscriptions_data needs to be augmented as we go, to handle transaction chaining. reinscriptions_data.insert(traversal.ordinal_number, traversal.get_inscription_id()); } @@ -603,9 +610,147 @@ async fn augment_transaction_with_ordinals_inscriptions_data( ); sequence_cursor.increment(is_cursed, db_tx).await?; } - tx.metadata - .ordinal_operations - .append(&mut mutated_operations); + tx.metadata.ordinal_operations.append(&mut mut_operations); - Ok(any_event) + Ok(true) +} + +#[cfg(test)] +mod test { + use std::collections::BTreeMap; + + use test_case::test_case; + + use chainhook_postgres::{pg_begin, pg_pool_client}; + use chainhook_sdk::utils::Context; + use chainhook_types::{ + bitcoin::{OutPoint, TxIn, TxOut}, + OrdinalInscriptionCurseType, OrdinalInscriptionNumber, OrdinalInscriptionRevealData, + OrdinalOperation, TransactionIdentifier, + }; + use ord::charm::Charm; + + use crate::{ + core::{ + protocol::{satoshi_numbering::TraversalResult, sequence_cursor::SequenceCursor}, + test_builders::{TestBlockBuilder, TestTransactionBuilder}, + }, + db::{ordinals_pg, pg_reset_db, pg_test_connection, pg_test_connection_pool}, + }; + + use super::update_block_inscriptions_with_consensus_sequence_data; + + #[test_case((884207, false, 1262349832364434, "0x5120694b38ea24908e86a857279105c376a82cd1556f51655abb2ebef398b57daa8b".into()) => Ok(vec![]); "common sat")] + #[test_case((884207, false, 0, "0x5120694b38ea24908e86a857279105c376a82cd1556f51655abb2ebef398b57daa8b".into()) => Ok(vec![Charm::Coin, Charm::Mythic, Charm::Palindrome]); "mythic sat")] + #[test_case((884207, false, 1050000000000000, "0x5120694b38ea24908e86a857279105c376a82cd1556f51655abb2ebef398b57daa8b".into()) => Ok(vec![Charm::Coin, Charm::Epic]); "epic sat")] + #[test_case((884207, false, 123454321, "0x5120694b38ea24908e86a857279105c376a82cd1556f51655abb2ebef398b57daa8b".into()) => Ok(vec![Charm::Palindrome]); "palindrome sat")] + #[test_case((884207, false, 1262349832364434, "0x00".into()) => Ok(vec![Charm::Burned]); "burned inscription")] + #[test_case((780000, true, 1262349832364434, "0x5120694b38ea24908e86a857279105c376a82cd1556f51655abb2ebef398b57daa8b".into()) => Ok(vec![Charm::Cursed]); "cursed inscription")] + #[test_case((884207, true, 1262349832364434, "0x5120694b38ea24908e86a857279105c376a82cd1556f51655abb2ebef398b57daa8b".into()) => Ok(vec![Charm::Vindicated]); "vindicated inscription")] + #[tokio::test] + async fn inscription_charms( + (block_height, cursed, ordinal_number, script_pubkey): (u64, bool, u64, String), + ) -> Result, String> { + let ctx = Context::empty(); + let mut sequence_cursor = SequenceCursor::new(); + let mut cache_l1 = BTreeMap::new(); + let tx_id = TransactionIdentifier { + hash: "b4722ad74e7092a194e367f2ec0609994ef7a006db4f9b9d055b46cfb6514e06".into(), + }; + cache_l1.insert( + (tx_id.clone(), 0, 0), + TraversalResult { + inscription_number: OrdinalInscriptionNumber { + classic: if cursed { -1 } else { 0 }, + jubilee: 0, + }, + inscription_input_index: 0, + transaction_identifier_inscription: tx_id, + ordinal_number, + transfers: 0, + }, + ); + let mut pg_client = pg_test_connection().await; + ordinals_pg::migrate(&mut pg_client).await?; + let result = { + let mut ord_client = pg_pool_client(&pg_test_connection_pool()).await?; + let client = pg_begin(&mut ord_client).await?; + + let mut block = TestBlockBuilder::new() + .height(block_height) + // Coinbase + .add_transaction(TestTransactionBuilder::new().build()) + .add_transaction( + TestTransactionBuilder::new() + .hash( + "b4722ad74e7092a194e367f2ec0609994ef7a006db4f9b9d055b46cfb6514e06" + .into(), + ) + .add_input(TxIn { + previous_output: OutPoint { + txid: TransactionIdentifier { hash: "f181aa98f2572879bd02278c72c83c7eaac2db82af713d1d239fc41859b2a26e".into() }, + vout: 0, + value: 10000, + block_height: 884200, + }, + script_sig: "0x00".into(), + sequence: 0, + witness: vec!["0x00".into()], + }) + .add_output(TxOut { value: 8000, script_pubkey }) + .add_ordinal_operation(OrdinalOperation::InscriptionRevealed( + OrdinalInscriptionRevealData { + content_bytes: "0x101010".into(), + content_type: "text/plain".into(), + content_length: 3, + inscription_number: OrdinalInscriptionNumber { + classic: if cursed { -1 } else { 0 }, + jubilee: 0, + }, + inscription_fee: 0, + inscription_output_value: 0, + inscription_id: "".into(), + inscription_input_index: 0, + inscription_pointer: Some(0), + inscriber_address: Some("bc1pd99n363yjz8gd2zhy7gstsmk4qkdz4t029j44wewhmee3dta429sm5xqrd".into()), + delegate: None, + metaprotocol: None, + metadata: None, + parents: vec![], + ordinal_number: 0, + ordinal_block_height: 0, + ordinal_offset: 0, + tx_index: 0, + transfers_pre_inscription: 0, + satpoint_post_inscription: "".into(), + curse_type: if cursed { Some(OrdinalInscriptionCurseType::Generic) } else { None }, + charms: 0, + }, + )) + .build(), + ) + .build(); + + update_block_inscriptions_with_consensus_sequence_data( + &mut block, + &mut sequence_cursor, + &mut cache_l1, + &client, + &ctx, + ) + .await?; + + let result = &block.transactions[1].metadata.ordinal_operations[0]; + // println!("{:?}", result); + let charms = match result { + OrdinalOperation::InscriptionRevealed(data) => data.charms, + _ => unreachable!(), + }; + // println!("{:?}", Charm::charms(charms)); + Ok(Charm::charms(charms)) + }; + pg_reset_db(&mut pg_client).await?; + + result + } } diff --git a/components/ordhook-core/src/core/test_builders.rs b/components/ordhook-core/src/core/test_builders.rs index 6b206dd..24118d1 100644 --- a/components/ordhook-core/src/core/test_builders.rs +++ b/components/ordhook-core/src/core/test_builders.rs @@ -1,5 +1,8 @@ use chainhook_types::{ - bitcoin::{OutPoint, TxIn, TxOut}, BitcoinBlockData, BitcoinBlockMetadata, BitcoinNetwork, BitcoinTransactionData, BitcoinTransactionMetadata, BlockIdentifier, Brc20Operation, OrdinalInscriptionCharms, OrdinalInscriptionNumber, OrdinalInscriptionRevealData, OrdinalOperation, TransactionIdentifier + bitcoin::{OutPoint, TxIn, TxOut}, + BitcoinBlockData, BitcoinBlockMetadata, BitcoinNetwork, BitcoinTransactionData, + BitcoinTransactionMetadata, BlockIdentifier, Brc20Operation, OrdinalInscriptionNumber, + OrdinalInscriptionRevealData, OrdinalOperation, TransactionIdentifier, }; pub struct TestBlockBuilder { @@ -101,7 +104,7 @@ impl TestTransactionBuilder { transfers_pre_inscription: 0, satpoint_post_inscription: "b61b0172d95e266c18aea0c624db987e971a5d6d4ebc2aaed85da4642d635735:0:0".to_string(), curse_type: None, - charms: OrdinalInscriptionCharms::none(), + charms: 0, }, )]; tx diff --git a/components/ordhook-core/src/db/models/db_inscription.rs b/components/ordhook-core/src/db/models/db_inscription.rs index cd999db..cbc0ecd 100644 --- a/components/ordhook-core/src/db/models/db_inscription.rs +++ b/components/ordhook-core/src/db/models/db_inscription.rs @@ -32,6 +32,7 @@ pub struct DbInscription { pub metaprotocol: Option, pub delegate: Option, pub timestamp: PgBigIntU32, + pub charms: PgBigIntU32, } impl DbInscription { @@ -82,6 +83,7 @@ impl DbInscription { metaprotocol: reveal.metaprotocol.clone(), delegate: reveal.delegate.clone(), timestamp: PgBigIntU32(timestamp), + charms: PgBigIntU32(reveal.charms as u32), } } } @@ -111,6 +113,7 @@ impl FromPgRow for DbInscription { metaprotocol: row.get("metaprotocol"), delegate: row.get("delegate"), timestamp: row.get("timestamp"), + charms: row.get("charms"), } } } diff --git a/components/ordhook-core/src/db/models/db_inscription_recursion.rs b/components/ordhook-core/src/db/models/db_inscription_recursion.rs index f6d8417..445cf6d 100644 --- a/components/ordhook-core/src/db/models/db_inscription_recursion.rs +++ b/components/ordhook-core/src/db/models/db_inscription_recursion.rs @@ -33,7 +33,7 @@ impl DbInscriptionRecursion { #[cfg(test)] mod test { - use chainhook_types::{OrdinalInscriptionCharms, OrdinalInscriptionNumber, OrdinalInscriptionRevealData}; + use chainhook_types::{OrdinalInscriptionNumber, OrdinalInscriptionRevealData}; use super::DbInscriptionRecursion; @@ -61,7 +61,7 @@ mod test { transfers_pre_inscription: 0, satpoint_post_inscription: "e47a70a218dfa746ba410b1c057403bb481523d830562fd8dec61ec4d2915e5f:0:0".to_string(), curse_type: None, - charms: OrdinalInscriptionCharms::none(), + charms: 0, }; let recursions = DbInscriptionRecursion::from_reveal(&reveal).unwrap(); assert_eq!(2, recursions.len()); diff --git a/components/ordhook-core/src/db/ordinals_pg.rs b/components/ordhook-core/src/db/ordinals_pg.rs index fc984a8..0660c30 100644 --- a/components/ordhook-core/src/db/ordinals_pg.rs +++ b/components/ordhook-core/src/db/ordinals_pg.rs @@ -260,15 +260,16 @@ async fn insert_inscriptions( params.push(&row.metaprotocol); params.push(&row.delegate); params.push(&row.timestamp); + params.push(&row.charms); } client .query( &format!("INSERT INTO inscriptions (inscription_id, ordinal_number, number, classic_number, block_height, block_hash, tx_id, tx_index, address, mime_type, content_type, content_length, content, fee, curse_type, recursive, input_index, pointer, metadata, - metaprotocol, delegate, timestamp) + metaprotocol, delegate, timestamp, charms) VALUES {} - ON CONFLICT (number) DO NOTHING", utils::multi_row_query_param_str(chunk.len(), 22)), + ON CONFLICT (number) DO NOTHING", utils::multi_row_query_param_str(chunk.len(), 23)), ¶ms, ) .await @@ -1010,7 +1011,8 @@ mod test { FromPgRow, }; use chainhook_types::{ - OrdinalInscriptionCharms, OrdinalInscriptionNumber, OrdinalInscriptionRevealData, OrdinalInscriptionTransferData, OrdinalInscriptionTransferDestination, OrdinalOperation + OrdinalInscriptionNumber, OrdinalInscriptionRevealData, OrdinalInscriptionTransferData, + OrdinalInscriptionTransferDestination, OrdinalOperation, }; use deadpool_postgres::GenericClient; @@ -1203,7 +1205,7 @@ mod test { transfers_pre_inscription: 0, satpoint_post_inscription: "b61b0172d95e266c18aea0c624db987e971a5d6d4ebc2aaed85da4642d635735:0:0".to_string(), curse_type: None, - charms: OrdinalInscriptionCharms::none(), + charms: 0, }, )) .build() diff --git a/migrations/ordinals/V16__inscription_charms.sql b/migrations/ordinals/V16__inscription_charms.sql new file mode 100644 index 0000000..6affa07 --- /dev/null +++ b/migrations/ordinals/V16__inscription_charms.sql @@ -0,0 +1 @@ +ALTER TABLE inscriptions ADD COLUMN charms BIGINT NOT NULL DEFAULT 0;