Skip to content

Commit

Permalink
Mint / burn queries
Browse files Browse the repository at this point in the history
  • Loading branch information
gostkin committed Jan 16, 2024
1 parent f44fd33 commit 616974f
Show file tree
Hide file tree
Showing 6 changed files with 409 additions and 0 deletions.
151 changes: 151 additions & 0 deletions webserver/server/app/controllers/MintBurnHistoryController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { Body, Controller, TsoaResponse, Res, Post, Route, SuccessResponse } from 'tsoa';
import { StatusCodes } from 'http-status-codes';
import tx from 'pg-tx';
import pool from '../services/PgPoolSingleton';

import type { ErrorShape } from '../../../shared/errors';
import type { EndpointTypes } from '../../../shared/routes';
import { Routes } from '../../../shared/routes';
import { mintBurnRange, mintBurnRangeByPolicyIds } from '../services/MintBurnHistoryService';
import type { MintBurnHistoryRequest, MintBurnHistoryResponse, MintBurnSingleResponse } from "../../../shared/models/MintBurn";
import type { PolicyId } from "../../../shared/models/PolicyIdAssetMap";
import type { ISqlMintBurnRangeResult, ISqlMintBurnRangeByPolicyIdsResult } from "../models/asset/mintBurnHistory.queries";

const route = Routes.mintBurnHistory;

@Route('asset/mint-burn-history')
export class MintRangeController extends Controller {
@SuccessResponse(`${StatusCodes.OK}`)
@Post()
public async projectedNftRange(
@Body()
requestBody: EndpointTypes[typeof route]['input'],
@Res()
errorResponse: TsoaResponse<
StatusCodes.BAD_REQUEST | StatusCodes.CONFLICT | StatusCodes.UNPROCESSABLE_ENTITY,
ErrorShape
>
): Promise<EndpointTypes[typeof route]['response']> {
if (requestBody.policyIds !== undefined && requestBody.policyIds.length > 0) {
return await this.handle_by_policy_ids_query(requestBody.policyIds, requestBody);
} else {
return await this.handle_general_query(requestBody);
}
}

async handle_general_query(
requestBody: EndpointTypes[typeof route]['input'],
): Promise<EndpointTypes[typeof route]['response']> {
const assets = await tx<
ISqlMintBurnRangeResult[]
>(pool, async dbTx => {
const data = await mintBurnRange({
range: requestBody.range,
dbTx
});

return data;
});

let mintRangeResponse: MintBurnSingleResponse = {
actionTxId: "",
actionBlockId: "",
metadata: null,
actionSlot: 0,
assets: {},
};

const result: MintBurnSingleResponse[] = [];

for (const entry of assets) {
const policyId = entry.policy_id !== null ? entry.policy_id.toString() : "";
const assetName = entry.asset_name !== null ? entry.asset_name.toString() : "";
const actionTxId = entry.action_tx_id !== null ? entry.action_tx_id.toString() : "";
const actionBlockId = entry.action_block_id !== null ? entry.action_block_id.toString() : "";

if (mintRangeResponse.actionTxId != actionTxId) {
if (mintRangeResponse.actionTxId.length > 0) {
result.push(mintRangeResponse);
}

mintRangeResponse = {
actionSlot: entry.action_slot,
actionTxId: actionTxId,
actionBlockId: actionBlockId,
metadata: entry.action_tx_metadata,
assets: {},
}
}

const for_policy = mintRangeResponse.assets[policyId] ?? {};

for_policy[assetName] = entry.amount;
mintRangeResponse.assets[policyId] = for_policy;
}

if (mintRangeResponse.actionTxId.length > 0) {
result.push(mintRangeResponse);
}

return result;
}

async handle_by_policy_ids_query(
policyIds: PolicyId[],
requestBody: EndpointTypes[typeof route]['input'],
): Promise<EndpointTypes[typeof route]['response']> {
const assets = await tx<
ISqlMintBurnRangeByPolicyIdsResult[]
>(pool, async dbTx => {
const data = await mintBurnRangeByPolicyIds({
range: requestBody.range,
policyIds: policyIds,
dbTx
});

return data;
});

let mintRangeResponse: MintBurnSingleResponse = {
actionTxId: "",
actionBlockId: "",
metadata: null,
actionSlot: 0,
assets: {},
};

const result: MintBurnSingleResponse[] = [];

for (const entry of assets) {
const policyId = entry.policy_id !== null ? entry.policy_id.toString() : "";
const assetName = entry.asset_name !== null ? entry.asset_name.toString() : "";
const actionTxId = entry.action_tx_id !== null ? entry.action_tx_id.toString() : "";
const actionBlockId = entry.action_block_id !== null ? entry.action_block_id.toString() : "";

if (mintRangeResponse.actionTxId != actionTxId) {
if (mintRangeResponse.actionTxId.length > 0) {
result.push(mintRangeResponse);
}

mintRangeResponse = {
actionSlot: entry.action_slot,
actionTxId: actionTxId,
actionBlockId: actionBlockId,
metadata: entry.action_tx_metadata,
assets: {},
}
}

const for_policy = mintRangeResponse.assets[policyId] ?? {};

for_policy[assetName] = entry.amount;
mintRangeResponse.assets[policyId] = for_policy;
}

if (mintRangeResponse.actionTxId.length > 0) {
result.push(mintRangeResponse);
}

return result;
}
}
111 changes: 111 additions & 0 deletions webserver/server/app/models/asset/mintBurnHistory.queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/** Types generated for queries found in "app/models/asset/mintBurnHistory.sql" */
import { PreparedQuery } from '@pgtyped/runtime';

/** 'SqlMintBurnRange' parameters type */
export interface ISqlMintBurnRangeParams {
max_slot: number;
min_slot: number;
}

/** 'SqlMintBurnRange' return type */
export interface ISqlMintBurnRangeResult {
action_block_id: string | null;
action_slot: number;
action_tx_id: string | null;
action_tx_metadata: string | null;
amount: string;
asset_name: string | null;
policy_id: string | null;
}

/** 'SqlMintBurnRange' query type */
export interface ISqlMintBurnRangeQuery {
params: ISqlMintBurnRangeParams;
result: ISqlMintBurnRangeResult;
}

const sqlMintBurnRangeIR: any = {"usedParamSet":{"min_slot":true,"max_slot":true},"params":[{"name":"min_slot","required":true,"transform":{"type":"scalar"},"locs":[{"a":793,"b":802}]},{"name":"max_slot","required":true,"transform":{"type":"scalar"},"locs":[{"a":828,"b":837}]}],"statement":"SELECT\n \"AssetMint\".amount as amount,\n encode(\"NativeAsset\".policy_id, 'hex') as policy_id,\n encode(\"NativeAsset\".asset_name, 'hex') as asset_name,\n encode(\"Transaction\".hash, 'hex') as action_tx_id,\n encode(\"Block\".hash, 'hex') as action_block_id,\n CASE\n WHEN \"TransactionMetadata\".payload = NULL THEN NULL\n ELSE encode(\"TransactionMetadata\".payload, 'hex')\n END AS action_tx_metadata,\n \"Block\".slot as action_slot\nFROM \"AssetMint\"\n LEFT JOIN \"TransactionMetadata\" ON \"TransactionMetadata\".id = \"AssetMint\".tx_id\n JOIN \"NativeAsset\" ON \"NativeAsset\".id = \"AssetMint\".asset_id\n JOIN \"Transaction\" ON \"Transaction\".id = \"AssetMint\".tx_id\n JOIN \"Block\" ON \"Transaction\".block_id = \"Block\".id\nWHERE\n \"Block\".slot > :min_slot!\n AND \"Block\".slot <= :max_slot!\nORDER BY (\"Block\".height, \"Transaction\".tx_index) ASC"};

/**
* Query generated from SQL:
* ```
* SELECT
* "AssetMint".amount as amount,
* encode("NativeAsset".policy_id, 'hex') as policy_id,
* encode("NativeAsset".asset_name, 'hex') as asset_name,
* encode("Transaction".hash, 'hex') as action_tx_id,
* encode("Block".hash, 'hex') as action_block_id,
* CASE
* WHEN "TransactionMetadata".payload = NULL THEN NULL
* ELSE encode("TransactionMetadata".payload, 'hex')
* END AS action_tx_metadata,
* "Block".slot as action_slot
* FROM "AssetMint"
* LEFT JOIN "TransactionMetadata" ON "TransactionMetadata".id = "AssetMint".tx_id
* JOIN "NativeAsset" ON "NativeAsset".id = "AssetMint".asset_id
* JOIN "Transaction" ON "Transaction".id = "AssetMint".tx_id
* JOIN "Block" ON "Transaction".block_id = "Block".id
* WHERE
* "Block".slot > :min_slot!
* AND "Block".slot <= :max_slot!
* ORDER BY ("Block".height, "Transaction".tx_index) ASC
* ```
*/
export const sqlMintBurnRange = new PreparedQuery<ISqlMintBurnRangeParams,ISqlMintBurnRangeResult>(sqlMintBurnRangeIR);


/** 'SqlMintBurnRangeByPolicyIds' parameters type */
export interface ISqlMintBurnRangeByPolicyIdsParams {
max_slot: number;
min_slot: number;
policy_ids: readonly (Buffer)[];
}

/** 'SqlMintBurnRangeByPolicyIds' return type */
export interface ISqlMintBurnRangeByPolicyIdsResult {
action_block_id: string | null;
action_slot: number;
action_tx_id: string | null;
action_tx_metadata: string | null;
amount: string;
asset_name: string | null;
policy_id: string | null;
}

/** 'SqlMintBurnRangeByPolicyIds' query type */
export interface ISqlMintBurnRangeByPolicyIdsQuery {
params: ISqlMintBurnRangeByPolicyIdsParams;
result: ISqlMintBurnRangeByPolicyIdsResult;
}

const sqlMintBurnRangeByPolicyIdsIR: any = {"usedParamSet":{"min_slot":true,"max_slot":true,"policy_ids":true},"params":[{"name":"policy_ids","required":true,"transform":{"type":"array_spread"},"locs":[{"a":874,"b":885}]},{"name":"min_slot","required":true,"transform":{"type":"scalar"},"locs":[{"a":793,"b":802}]},{"name":"max_slot","required":true,"transform":{"type":"scalar"},"locs":[{"a":828,"b":837}]}],"statement":"SELECT\n \"AssetMint\".amount as amount,\n encode(\"NativeAsset\".policy_id, 'hex') as policy_id,\n encode(\"NativeAsset\".asset_name, 'hex') as asset_name,\n encode(\"Transaction\".hash, 'hex') as action_tx_id,\n encode(\"Block\".hash, 'hex') as action_block_id,\n CASE\n WHEN \"TransactionMetadata\".payload = NULL THEN NULL\n ELSE encode(\"TransactionMetadata\".payload, 'hex')\n END AS action_tx_metadata,\n \"Block\".slot as action_slot\nFROM \"AssetMint\"\n LEFT JOIN \"TransactionMetadata\" ON \"TransactionMetadata\".id = \"AssetMint\".tx_id\n JOIN \"NativeAsset\" ON \"NativeAsset\".id = \"AssetMint\".asset_id\n JOIN \"Transaction\" ON \"Transaction\".id = \"AssetMint\".tx_id\n JOIN \"Block\" ON \"Transaction\".block_id = \"Block\".id\nWHERE\n \"Block\".slot > :min_slot!\n AND \"Block\".slot <= :max_slot!\n AND \"NativeAsset\".policy_id IN :policy_ids!\nORDER BY (\"Block\".height, \"Transaction\".tx_index) ASC"};

/**
* Query generated from SQL:
* ```
* SELECT
* "AssetMint".amount as amount,
* encode("NativeAsset".policy_id, 'hex') as policy_id,
* encode("NativeAsset".asset_name, 'hex') as asset_name,
* encode("Transaction".hash, 'hex') as action_tx_id,
* encode("Block".hash, 'hex') as action_block_id,
* CASE
* WHEN "TransactionMetadata".payload = NULL THEN NULL
* ELSE encode("TransactionMetadata".payload, 'hex')
* END AS action_tx_metadata,
* "Block".slot as action_slot
* FROM "AssetMint"
* LEFT JOIN "TransactionMetadata" ON "TransactionMetadata".id = "AssetMint".tx_id
* JOIN "NativeAsset" ON "NativeAsset".id = "AssetMint".asset_id
* JOIN "Transaction" ON "Transaction".id = "AssetMint".tx_id
* JOIN "Block" ON "Transaction".block_id = "Block".id
* WHERE
* "Block".slot > :min_slot!
* AND "Block".slot <= :max_slot!
* AND "NativeAsset".policy_id IN :policy_ids!
* ORDER BY ("Block".height, "Transaction".tx_index) ASC
* ```
*/
export const sqlMintBurnRangeByPolicyIds = new PreparedQuery<ISqlMintBurnRangeByPolicyIdsParams,ISqlMintBurnRangeByPolicyIdsResult>(sqlMintBurnRangeByPolicyIdsIR);


49 changes: 49 additions & 0 deletions webserver/server/app/models/asset/mintBurnHistory.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
@name sqlMintBurnRange
*/
SELECT
"AssetMint".amount as amount,
encode("NativeAsset".policy_id, 'hex') as policy_id,
encode("NativeAsset".asset_name, 'hex') as asset_name,
encode("Transaction".hash, 'hex') as action_tx_id,
encode("Block".hash, 'hex') as action_block_id,
CASE
WHEN "TransactionMetadata".payload = NULL THEN NULL
ELSE encode("TransactionMetadata".payload, 'hex')
END AS action_tx_metadata,
"Block".slot as action_slot
FROM "AssetMint"
LEFT JOIN "TransactionMetadata" ON "TransactionMetadata".id = "AssetMint".tx_id
JOIN "NativeAsset" ON "NativeAsset".id = "AssetMint".asset_id
JOIN "Transaction" ON "Transaction".id = "AssetMint".tx_id
JOIN "Block" ON "Transaction".block_id = "Block".id
WHERE
"Block".slot > :min_slot!
AND "Block".slot <= :max_slot!
ORDER BY ("Block".height, "Transaction".tx_index) ASC;

/*
@name sqlMintBurnRangeByPolicyIds
@param policy_ids -> (...)
*/
SELECT
"AssetMint".amount as amount,
encode("NativeAsset".policy_id, 'hex') as policy_id,
encode("NativeAsset".asset_name, 'hex') as asset_name,
encode("Transaction".hash, 'hex') as action_tx_id,
encode("Block".hash, 'hex') as action_block_id,
CASE
WHEN "TransactionMetadata".payload = NULL THEN NULL
ELSE encode("TransactionMetadata".payload, 'hex')
END AS action_tx_metadata,
"Block".slot as action_slot
FROM "AssetMint"
LEFT JOIN "TransactionMetadata" ON "TransactionMetadata".id = "AssetMint".tx_id
JOIN "NativeAsset" ON "NativeAsset".id = "AssetMint".asset_id
JOIN "Transaction" ON "Transaction".id = "AssetMint".tx_id
JOIN "Block" ON "Transaction".block_id = "Block".id
WHERE
"Block".slot > :min_slot!
AND "Block".slot <= :max_slot!
AND "NativeAsset".policy_id IN :policy_ids!
ORDER BY ("Block".height, "Transaction".tx_index) ASC;
26 changes: 26 additions & 0 deletions webserver/server/app/services/MintBurnHistoryService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { PoolClient } from 'pg';
import type { ISqlMintBurnRangeResult, ISqlMintBurnRangeByPolicyIdsResult } from '../models/asset/mintBurnHistory.queries';
import { sqlMintBurnRange, sqlMintBurnRangeByPolicyIds } from '../models/asset/mintBurnHistory.queries';
import type { PolicyId } from "../../../shared/models/PolicyIdAssetMap";

export async function mintBurnRange(request: {
range: { minSlot: number, maxSlot: number },
dbTx: PoolClient,
}): Promise<ISqlMintBurnRangeResult[]> {
return (await sqlMintBurnRange.run({
min_slot: request.range.minSlot,
max_slot: request.range.maxSlot,
}, request.dbTx));
}

export async function mintBurnRangeByPolicyIds(request: {
range: { minSlot: number, maxSlot: number },
policyIds: PolicyId[],
dbTx: PoolClient,
}): Promise<ISqlMintBurnRangeByPolicyIdsResult[]> {
return (await sqlMintBurnRangeByPolicyIds.run({
min_slot: request.range.minSlot,
max_slot: request.range.maxSlot,
policy_ids: request.policyIds.map(id => Buffer.from(id, 'hex')),
}, request.dbTx));
}
Loading

0 comments on commit 616974f

Please sign in to comment.