Skip to content

Commit

Permalink
feat!: handle transfer types and consider them for BRC-20 indexing (#258
Browse files Browse the repository at this point in the history
)

* fix: latest api toolkit

* feat: transfer type handling

* fix: latest chainhook client
  • Loading branch information
rafaelcr authored Nov 1, 2023
1 parent 2e4b497 commit 7b83761
Show file tree
Hide file tree
Showing 12 changed files with 256 additions and 134 deletions.
14 changes: 14 additions & 0 deletions migrations/1698856424356_locations-transfer-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { MigrationBuilder, ColumnDefinitions } from 'node-pg-migrate';

export const shorthands: ColumnDefinitions | undefined = undefined;

export function up(pgm: MigrationBuilder): void {
pgm.createType('transfer_type', ['transferred', 'spent_in_fees', 'burnt']);
pgm.addColumn('locations', {
transfer_type: {
type: 'transfer_type',
notNull: true,
},
});
}
104 changes: 27 additions & 77 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@
"@fastify/multipart": "^7.1.0",
"@fastify/swagger": "^8.3.1",
"@fastify/type-provider-typebox": "^3.2.0",
"@hirosystems/api-toolkit": "^1.1.0",
"@hirosystems/chainhook-client": "^1.3.1",
"@hirosystems/api-toolkit": "^1.3.0",
"@hirosystems/chainhook-client": "^1.4.0",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/commit-analyzer": "^10.0.4",
"@semantic-release/git": "^10.0.1",
Expand Down
51 changes: 34 additions & 17 deletions src/pg/brc20/brc20-pg-store.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { BasePgStoreModule, logger } from '@hirosystems/api-toolkit';
import * as postgres from 'postgres';
import { hexToBuffer } from '../../api/util/helpers';
import { DbInscription, DbInscriptionIndexPaging, DbLocation, DbPaginatedResult } from '../types';
import {
DbInscription,
DbInscriptionIndexPaging,
DbLocation,
DbLocationTransferType,
DbPaginatedResult,
} from '../types';
import {
BRC20_DEPLOYS_COLUMNS,
BRC20_OPERATIONS,
Expand All @@ -20,15 +26,7 @@ import {
DbBrc20TokenWithSupply,
DbBrc20TransferEvent,
} from './types';

import {
Brc20Deploy,
Brc20Mint,
Brc20Transfer,
brc20FromInscriptionContent,
isAddressSentAsFee,
} from './helpers';

import { Brc20Deploy, Brc20Mint, Brc20Transfer, brc20FromInscriptionContent } from './helpers';
import { Brc20TokenOrderBy } from '../../api/schemas';
import { objRemoveUndefinedValues } from '../helpers';

Expand All @@ -53,7 +51,8 @@ export class Brc20PgStore extends BasePgStoreModule {
const block = await sql<DbBrc20ScannedInscription[]>`
SELECT
EXISTS(SELECT location_id FROM genesis_locations WHERE location_id = l.id) AS genesis,
l.id, l.inscription_id, l.block_height, l.tx_id, l.tx_index, l.address
l.id, l.inscription_id, l.block_height, l.tx_id, l.tx_index, l.address,
l.transfer_type
FROM locations AS l
INNER JOIN inscriptions AS i ON l.inscription_id = i.id
WHERE l.block_height = ${blockHeight}
Expand All @@ -76,7 +75,7 @@ export class Brc20PgStore extends BasePgStoreModule {
if (writes.length === 0) return;
for (const write of writes) {
if (write.genesis) {
if (isAddressSentAsFee(write.address)) continue;
if (write.transfer_type != DbLocationTransferType.transferred) continue;
const content = await this.sql<{ content: string }[]>`
SELECT content FROM inscriptions WHERE id = ${write.inscription_id}
`;
Expand Down Expand Up @@ -113,9 +112,15 @@ export class Brc20PgStore extends BasePgStoreModule {
if (fromAddressRes.count === 0) return;
const fromAddress = fromAddressRes[0].from_address;
// Is this transfer sent as fee or from the same sender? If so, we'll return the balance.
// Is it burnt? Mark as empty owner.
const returnToSender =
isAddressSentAsFee(location.address) || fromAddress == location.address;
const toAddress = returnToSender ? fromAddress : location.address;
location.transfer_type == DbLocationTransferType.spentInFees ||
fromAddress == location.address;
const toAddress = returnToSender
? fromAddress
: location.transfer_type == DbLocationTransferType.burnt
? ''
: location.address;
// Check if we have a valid transfer inscription emitted by this address that hasn't been sent
// to another address before. Use `LIMIT 3` as a quick way of checking if we have just inserted
// the first transfer for this inscription (genesis + transfer).
Expand Down Expand Up @@ -226,7 +231,11 @@ export class Brc20PgStore extends BasePgStoreModule {
op: Brc20Deploy;
location: DbBrc20Location;
}): Promise<void> {
if (!deploy.location.inscription_id || isAddressSentAsFee(deploy.location.address)) return;
if (
deploy.location.transfer_type != DbLocationTransferType.transferred ||
!deploy.location.inscription_id
)
return;
const insert: DbBrc20DeployInsert = {
inscription_id: deploy.location.inscription_id,
block_height: deploy.location.block_height,
Expand Down Expand Up @@ -272,7 +281,11 @@ export class Brc20PgStore extends BasePgStoreModule {
}

private async insertMint(mint: { op: Brc20Mint; location: DbBrc20Location }): Promise<void> {
if (!mint.location.inscription_id || isAddressSentAsFee(mint.location.address)) return;
if (
mint.location.transfer_type != DbLocationTransferType.transferred ||
!mint.location.inscription_id
)
return;
// Check the following conditions:
// * Is the mint amount within the allowed token limits?
// * Is the number of decimals correct?
Expand Down Expand Up @@ -349,7 +362,11 @@ export class Brc20PgStore extends BasePgStoreModule {
op: Brc20Transfer;
location: DbBrc20Location;
}): Promise<void> {
if (!transfer.location.inscription_id || isAddressSentAsFee(transfer.location.address)) return;
if (
transfer.location.transfer_type != DbLocationTransferType.transferred ||
!transfer.location.inscription_id
)
return;
// Check the following conditions:
// * Do we have enough available balance to do this transfer?
const transferRes = await this.sql`
Expand Down
4 changes: 0 additions & 4 deletions src/pg/brc20/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,3 @@ export function brc20FromInscriptionContent(content: string): Brc20 | undefined
// Not a BRC-20 inscription.
}
}

export function isAddressSentAsFee(address: string | null): boolean {
return address === null || address.length === 0;
}
3 changes: 3 additions & 0 deletions src/pg/brc20/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { DbLocationTransferType } from '../types';

export type DbBrc20Location = {
id: string;
inscription_id: string | null;
block_height: string;
tx_id: string;
tx_index: number;
address: string | null;
transfer_type: DbLocationTransferType;
};

export type DbBrc20ScannedInscription = DbBrc20Location & {
Expand Down
9 changes: 8 additions & 1 deletion src/pg/pg-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ import {
DbLocation,
DbLocationPointer,
DbLocationPointerInsert,
DbLocationTransferType,
DbPaginatedResult,
DbRevealInsert,
INSCRIPTIONS_COLUMNS,
LOCATIONS_COLUMNS,
} from './types';
import { toEnumValue } from '@hirosystems/api-toolkit';

export const MIGRATIONS_DIR = path.join(__dirname, '../../migrations');

Expand Down Expand Up @@ -154,6 +156,7 @@ export class PgStore extends BasePgStore {
prev_offset: null,
value: reveal.inscription_output_value.toString(),
timestamp: event.timestamp,
transfer_type: DbLocationTransferType.transferred,
},
recursive_refs,
});
Expand Down Expand Up @@ -191,6 +194,7 @@ export class PgStore extends BasePgStore {
prev_offset: null,
value: reveal.inscription_output_value.toString(),
timestamp: event.timestamp,
transfer_type: DbLocationTransferType.transferred,
},
recursive_refs,
});
Expand All @@ -206,7 +210,7 @@ export class PgStore extends BasePgStore {
tx_id,
tx_index: transfer.tx_index,
genesis_id: transfer.inscription_id,
address: transfer.updated_address,
address: transfer.destination.value ?? null,
output: `${satpoint.tx_id}:${satpoint.vout}`,
offset: satpoint.offset ?? null,
prev_output: `${prevSatpoint.tx_id}:${prevSatpoint.vout}`,
Expand All @@ -215,6 +219,9 @@ export class PgStore extends BasePgStore {
? transfer.post_transfer_output_value.toString()
: null,
timestamp: event.timestamp,
transfer_type:
toEnumValue(DbLocationTransferType, transfer.destination.type) ??
DbLocationTransferType.transferred,
},
});
}
Expand Down
7 changes: 7 additions & 0 deletions src/pg/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ export type DbFullyLocatedInscriptionResult = {
recursion_refs: string | null;
};

export enum DbLocationTransferType {
transferred = 'transferred',
spentInFees = 'spent_in_fees',
burnt = 'burnt',
}

export type DbLocationInsert = {
genesis_id: string;
block_height: number;
Expand All @@ -47,6 +53,7 @@ export type DbLocationInsert = {
prev_offset: PgNumeric | null;
value: PgNumeric | null;
timestamp: number;
transfer_type: DbLocationTransferType;
};

export type DbLocation = {
Expand Down
Loading

0 comments on commit 7b83761

Please sign in to comment.