Skip to content

Commit

Permalink
feat: start storing token deploys
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaelcr committed May 16, 2023
1 parent 60687db commit bf4c7f6
Show file tree
Hide file tree
Showing 6 changed files with 405 additions and 21 deletions.
5 changes: 2 additions & 3 deletions migrations/1684174644336_brc20-deploys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,9 @@ export function up(pgm: MigrationBuilder): void {
},
limit: {
type: 'numeric',
notNull: true,
},
decimals: {
type: 'numeric',
type: 'int',
notNull: true,
},
});
Expand All @@ -47,7 +46,7 @@ export function up(pgm: MigrationBuilder): void {
'brc20_deploys_inscription_id_fk',
'FOREIGN KEY(inscription_id) REFERENCES inscriptions(id) ON DELETE CASCADE'
);
pgm.createConstraint('brc20_deploys', 'brc20_deploys_ticker_unique', 'UNIQUE(ticker)');
pgm.createIndex('brc20_deploys', 'LOWER(ticker)', { unique: true });
pgm.createIndex('brc20_deploys', ['block_height']);
pgm.createIndex('brc20_deploys', ['address']);
}
56 changes: 53 additions & 3 deletions src/pg/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,54 @@ import {
DbInscriptionInsert,
} from './types';

const OpJson = Type.Object(
const OpJsonSchema = Type.Object(
{
p: Type.String(),
op: Type.String(),
},
{ additionalProperties: true }
);
const OpJsonC = TypeCompiler.Compile(OpJson);
export type OpJson = Static<typeof OpJson>;
const OpJsonC = TypeCompiler.Compile(OpJsonSchema);
export type OpJson = Static<typeof OpJsonSchema>;

const Brc20DeploySchema = Type.Object({
p: Type.Literal('brc-20'),
op: Type.Literal('deploy'),
tick: Type.String(),
max: Type.String(),
lim: Type.Optional(Type.String()),
dec: Type.Optional(Type.String()),
});
const Brc20DeployC = TypeCompiler.Compile(Brc20DeploySchema);
export type Brc20Deploy = Static<typeof Brc20DeploySchema>;

const Brc20MintSchema = Type.Object({
p: Type.Literal('brc-20'),
op: Type.Literal('mint'),
tick: Type.String(),
amt: Type.String(),
});
const Brc20MintC = TypeCompiler.Compile(Brc20MintSchema);
export type Brc20Mint = Static<typeof Brc20MintSchema>;

const Brc20TransferSchema = Type.Object({
p: Type.Literal('brc-20'),
op: Type.Literal('transfer'),
tick: Type.String(),
amt: Type.String(),
});
const Brc20TransferC = TypeCompiler.Compile(Brc20TransferSchema);
export type Brc20Transfer = Static<typeof Brc20TransferSchema>;

const Brc20Schema = Type.Union([Brc20DeploySchema, Brc20MintSchema, Brc20TransferSchema]);
// const Brc20C = TypeCompiler.Compile(Brc20Schema);
export type Brc20 = Static<typeof Brc20Schema>;

/**
* Tries to parse a text inscription into an OpJson schema.
* @param inscription - Inscription content
* @returns OpJson
*/
export function inscriptionContentToJson(inscription: DbInscriptionInsert): OpJson | undefined {
if (
inscription.mime_type.startsWith('text/plain') ||
Expand All @@ -37,6 +75,18 @@ export function inscriptionContentToJson(inscription: DbInscriptionInsert): OpJs
}
}

export function brc20DeployFromOpJson(json: OpJson): Brc20Deploy | undefined {
if (Brc20DeployC.Check(json)) {
return json;
}
}

export function brc20MintFromOpJson(json: OpJson): Brc20Mint | undefined {
if (Brc20MintC.Check(json)) {
return json;
}
}

/**
* Returns which inscription count is required based on filters sent to the index endpoint.
* @param filters - DbInscriptionIndexFilters
Expand Down
117 changes: 102 additions & 15 deletions src/pg/pg-store.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import { Order, OrderBy } from '../api/schemas';
import { normalizedHexString } from '../api/util/helpers';
import { OrdinalSatoshi, SatoshiRarity } from '../api/util/ordinal-satoshi';
import { ChainhookPayload, InscriptionEvent } from '../chainhook/schemas';
import { ChainhookPayload } from '../chainhook/schemas';
import { ENV } from '../env';
import { logger } from '../logger';
import { getIndexResultCountType, inscriptionContentToJson } from './helpers';
import {
Brc20Deploy,
Brc20Mint,
brc20DeployFromOpJson,
brc20MintFromOpJson,
getIndexResultCountType,
inscriptionContentToJson,
} from './helpers';
import { runMigrations } from './migrations';
import { connectPostgres } from './postgres-tools';
import { BasePgStore } from './postgres-tools/base-pg-store';
import {
BRC20_DEPLOYS_COLUMNS,
DbBrc20Deploy,
DbFullyLocatedInscriptionResult,
DbInscriptionContent,
DbInscriptionIndexFilters,
Expand Down Expand Up @@ -474,6 +483,18 @@ export class PgStore extends BasePgStore {
}
}

async getBrc20Deploy(args: { ticker: string }): Promise<DbBrc20Deploy | undefined> {
const results = await this.sql<DbBrc20Deploy[]>`
SELECT ${this.sql(BRC20_DEPLOYS_COLUMNS)}
FROM brc20_deploys
WHERE LOWER(ticker) = LOWER(${args.ticker})
LIMIT 1
`;
if (results.count === 1) {
return results[0];
}
}

async refreshMaterializedView(viewName: string) {
const isProd = process.env.NODE_ENV === 'production';
await this.sql`REFRESH MATERIALIZED VIEW ${
Expand Down Expand Up @@ -553,21 +574,20 @@ export class PgStore extends BasePgStore {
sat_coinbase_height = EXCLUDED.sat_coinbase_height,
timestamp = EXCLUDED.timestamp
`;
// TODO: No valid action can occur via the spending of an ordinal via transaction fee. If it
// occurs during the inscription process then the resulting inscription is ignored. If it
// occurs during the second phase of the transfer process, the balance is returned to the
// senders available balance.
const json = inscriptionContentToJson(args.inscription);
if (json) {
const values = {
inscription_id,
p: json.p,
op: json.op,
content: json,
};
await sql`
INSERT INTO json_contents ${sql(values)}
ON CONFLICT ON CONSTRAINT json_contents_inscription_id_unique DO UPDATE SET
p = EXCLUDED.p,
op = EXCLUDED.op,
content = EXCLUDED.content
`;
// Is this a BRC-20 operation?
const deploy = brc20DeployFromOpJson(json);
if (deploy) {
await this.insertBrc20Deploy({ deploy, inscription_id, location: args.location });
} else {
const mint = brc20MintFromOpJson(json);
if (mint) await this.insertBrc20Mint({ mint, inscription_id, location: args.location });
}
}
});
return inscription_id;
Expand Down Expand Up @@ -671,4 +691,71 @@ export class PgStore extends BasePgStore {
}
});
}

private async insertBrc20Deploy(args: {
deploy: Brc20Deploy;
inscription_id: number;
location: DbLocationInsert;
}): Promise<void> {
const deploy = {
inscription_id: args.inscription_id,
block_height: args.location.block_height,
tx_id: args.location.tx_id,
address: args.location.address,
ticker: args.deploy.tick,
max: args.deploy.max,
limit: args.deploy.lim ?? null,
decimals: args.deploy.dec ?? 18,
};
const insertion = await this.sql`
INSERT INTO brc20_deploys ${this.sql(deploy)}
ON CONFLICT (LOWER(ticker)) DO NOTHING
`;
if (insertion.count > 0) {
logger.info(
`PgStore [BRC-20] inserted deploy for ${args.deploy.tick} at block ${args.location.block_height}`
);
} else {
logger.debug(
`PgStore [BRC-20] attempted to insert deploy for ${args.deploy.tick} at block ${args.location.block_height} but a previous entry existed`
);
}
}

private async insertBrc20Mint(args: {
mint: Brc20Mint;
inscription_id: number;
location: DbLocationInsert;
}): Promise<void> {
await this.sqlWriteTransaction(async sql => {
// Get which token this belongs to
const deploy = await sql<{ id: number }[]>`
SELECT id FROM brc20_deploys WHERE ticker = ${args.mint.tick}
`;
if (deploy.count === 0) {
logger.debug(
`PgStore [BRC-20] attempted to insert mint for non-deployed token ${args.mint.tick} at block ${args.location.block_height}`
);
return;
}
const deploy_id = deploy[0].id;
// TODO: The first mint to exceed the maximum supply will receive the fraction that is valid.
// (ex. 21,000,000 maximum supply, 20,999,242 circulating supply, and 1000 mint inscription =
// 758 balance state applied)

// TODO: Check limit per mint
const mint = {
inscription_id: args.inscription_id,
brc20_deploy_id: deploy_id,
block_height: args.location.block_height,
tx_id: args.location.tx_id,
address: args.location.address,
amount: args.mint.amt,
};
await this.sql`INSERT INTO brc20_mints ${this.sql(mint)}`;
logger.info(
`PgStore [BRC-20] inserted mint for ${args.mint.tick} (${args.mint.amt}) at block ${args.location.block_height}`
);
});
}
}
24 changes: 24 additions & 0 deletions src/pg/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,27 @@ export enum DbInscriptionIndexResultCountType {
/** Filtered by custom arguments */
custom,
}

export type DbBrc20Deploy = {
id: string;
inscription_id: string;
block_height: string;
tx_id: string;
address: string;
ticker: string;
max: string;
limit?: string;
decimals: number;
};

export const BRC20_DEPLOYS_COLUMNS = [
'id',
'inscription_id',
'block_height',
'tx_id',
'address',
'ticker',
'max',
'limit',
'decimals',
];
Loading

0 comments on commit bf4c7f6

Please sign in to comment.