mirror of
https://github.com/alexgo-io/bitcoin-indexer.git
synced 2026-01-12 08:34:17 +08:00
@@ -342,6 +342,7 @@ export const InscriptionResponse = Type.Object(
|
||||
examples: ['brc20'],
|
||||
})
|
||||
),
|
||||
charms: Type.Array(Type.String({ examples: ['coin', 'palindrome', 'vindicated'] })),
|
||||
},
|
||||
{ title: 'Inscription Response' }
|
||||
);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -37,6 +37,7 @@ export type DbFullyLocatedInscriptionResult = {
|
||||
pointer: number | null;
|
||||
metaprotocol: string | null;
|
||||
delegate: string | null;
|
||||
charms: string;
|
||||
};
|
||||
|
||||
export type DbLocation = {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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: [],
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<OrdinalInscriptionCurseType>,
|
||||
pub charms: OrdinalInscriptionCharms,
|
||||
pub charms: u16,
|
||||
}
|
||||
|
||||
impl OrdinalInscriptionNumber {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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?;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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<bool, String> {
|
||||
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::<Vec<u64>>();
|
||||
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<Vec<Charm>, 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -32,6 +32,7 @@ pub struct DbInscription {
|
||||
pub metaprotocol: Option<String>,
|
||||
pub delegate: Option<String>,
|
||||
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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -260,15 +260,16 @@ async fn insert_inscriptions<T: GenericClient>(
|
||||
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()
|
||||
|
||||
1
migrations/ordinals/V16__inscription_charms.sql
Normal file
1
migrations/ordinals/V16__inscription_charms.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE inscriptions ADD COLUMN charms BIGINT NOT NULL DEFAULT 0;
|
||||
Reference in New Issue
Block a user