Skip to content

Commit

Permalink
Mint / burn
Browse files Browse the repository at this point in the history
  • Loading branch information
gostkin committed Jan 10, 2024
1 parent 36229c0 commit 3a9e9d0
Show file tree
Hide file tree
Showing 6 changed files with 269 additions and 72 deletions.
107 changes: 91 additions & 16 deletions webserver/server/app/controllers/MintRangeController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import { Errors } from '../../../shared/errors';
import type { ErrorShape } from '../../../shared/errors';
import type { EndpointTypes } from '../../../shared/routes';
import { Routes } from '../../../shared/routes';
import { MINT_RANGE_LIMIT } from "../../../shared/constants";
import { mintRange } from '../services/MintRangeService';
import type {MintRangeResponse} from "../../../shared/models/MintRange";
import { MINT_BURN_LIMIT } from "../../../shared/constants";
import { mintRange, mintRangeByPolicyIds } from '../services/MintRangeService';
import type { MintRangeResponse, MintRangeSingleResponse } from "../../../shared/models/MintRange";
import type { PolicyId } from "../../../shared/models/PolicyIdAssetMap";
import type { ISqlMintRangeResult } from "../models/asset/sqlMintRange.queries";

const route = Routes.mintRange;

Expand All @@ -27,35 +29,99 @@ export class MintRangeController extends Controller {
ErrorShape
>
): Promise<EndpointTypes[typeof route]['response']> {
const slotRangeSize = requestBody.range.maxSlot - requestBody.range.minSlot;
if (slotRangeSize > MINT_RANGE_LIMIT.SLOT_RANGE) {
const after = requestBody.after != undefined ? requestBody.after : 0;
const until = requestBody.untilSlot != undefined ? requestBody.untilSlot : Number.MAX_VALUE;
const limit = requestBody.limit != undefined ? requestBody.limit : MINT_BURN_LIMIT.MAX_LIMIT;

if (limit > MINT_BURN_LIMIT.MAX_LIMIT) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return errorResponse(
StatusCodes.BAD_REQUEST,
genErrorMessage(Errors.SlotRangeLimitExceeded, {
limit: MINT_RANGE_LIMIT.SLOT_RANGE,
found: slotRangeSize,
limit: MINT_BURN_LIMIT.MAX_LIMIT,
found: limit,
})
);
}

if (requestBody.policyIds !== undefined && requestBody.policyIds.length > 0) {

let params = {
afterSlot: after,
untilSlot: until,
limit: limit
};

return await this.handle_by_address_query(requestBody.address, requestBody);
if (requestBody.policyIds !== undefined && requestBody.policyIds.length > 0) {
return await this.handle_by_policy_ids_query(requestBody.policyIds, params);
} else {
return await this.handle_general_query(requestBody);
return await this.handle_general_query(params);
}
}

async handle_general_query(
requestBody: EndpointTypes[typeof route]['input'],
params: { afterSlot: number, untilSlot: number, limit: number },
): Promise<EndpointTypes[typeof route]['response']> {
const response = await tx<
MintRangeResponse
const assets = await tx<
ISqlMintRangeResult[]
>(pool, async dbTx => {
const data = await mintRange({
range: requestBody.range,
params: params,
dbTx
});

return data;
});

let mintRangeResponse: MintRangeSingleResponse = {
actionTxId: "",
actionSlot: 0,
assets: {},
};

const result: MintRangeSingleResponse[] = [];
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() : "";
if (mintRangeResponse.actionTxId != actionTxId) {
if (mintRangeResponse.actionTxId.length > 0) {
result.push(mintRangeResponse);
}

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

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

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

let after = undefined;

if (result.length >= params.limit) {
after = result[result.length - 1].actionSlot;
}

return {
result: result,
after: after,
};
}

async handle_by_policy_ids_query(
policyIds: PolicyId[],
params: { afterSlot: number, untilSlot: number, limit: number },
): Promise<EndpointTypes[typeof route]['response']> {
const result = await tx<
MintRangeSingleResponse[]
>(pool, async dbTx => {
const data = await mintRangeByPolicyIds({
params: params,
policyIds: policyIds,
dbTx
});
return data.map(data => ({
Expand All @@ -74,7 +140,16 @@ export class MintRangeController extends Controller {
}));
});

return response;
let after = undefined;

if (result.length >= params.limit) {
after = result[result.length - 1].actionSlot;
}

return {
result: result,
after: after,
};
}

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

/** 'SqlMintRange' parameters type */
export interface ISqlMintRangeParams {
limit: string;
max_slot: number;
min_slot: number;
}

/** 'SqlMintRange' return type */
export interface ISqlMintRangeResult {
action_slot: number;
action_tx_id: string | null;
amount: string;
asset_name: string | null;
policy_id: string | null;
}

/** 'SqlMintRange' query type */
export interface ISqlMintRangeQuery {
params: ISqlMintRangeParams;
result: ISqlMintRangeResult;
}

const sqlMintRangeIR: any = {"usedParamSet":{"min_slot":true,"max_slot":true,"limit":true},"params":[{"name":"min_slot","required":true,"transform":{"type":"scalar"},"locs":[{"a":490,"b":499},{"a":942,"b":951}]},{"name":"max_slot","required":true,"transform":{"type":"scalar"},"locs":[{"a":525,"b":534},{"a":989,"b":998}]},{"name":"limit","required":true,"transform":{"type":"scalar"},"locs":[{"a":1018,"b":1024}]}],"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 \"Block\".slot as action_slot\nFROM \"AssetMint\"\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 \"Block\".height <= (\n SELECT MAX(\"Heights\".height) FROM\n (SELECT \"Block\".height as height FROM \"AssetMint\"\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\n WHERE\n \"Block\".slot > :min_slot!\n AND \"Block\".slot <= :max_slot!\n LIMIT :limit!) AS \"Heights\"\n )\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,
* "Block".slot as action_slot
* FROM "AssetMint"
* 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 "Block".height <= (
* SELECT MAX("Heights".height) FROM
* (SELECT "Block".height as height FROM "AssetMint"
* 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!
* LIMIT :limit!) AS "Heights"
* )
* ORDER BY ("Block".height, "Transaction".tx_index) ASC
* ```
*/
export const sqlMintRange = new PreparedQuery<ISqlMintRangeParams,ISqlMintRangeResult>(sqlMintRangeIR);


/** 'SqlMintRangeByPolicyIds' parameters type */
export interface ISqlMintRangeByPolicyIdsParams {
limit: string;
max_slot: number;
min_slot: number;
policy_ids: readonly (Buffer)[];
}

/** 'SqlMintRangeByPolicyIds' return type */
export interface ISqlMintRangeByPolicyIdsResult {
action_slot: number;
action_tx_id: string | null;
amount: string;
asset_name: string | null;
policy_id: string | null;
}

/** 'SqlMintRangeByPolicyIds' query type */
export interface ISqlMintRangeByPolicyIdsQuery {
params: ISqlMintRangeByPolicyIdsParams;
result: ISqlMintRangeByPolicyIdsResult;
}

const sqlMintRangeByPolicyIdsIR: any = {"usedParamSet":{"min_slot":true,"max_slot":true,"policy_ids":true,"limit":true},"params":[{"name":"policy_ids","required":true,"transform":{"type":"array_spread"},"locs":[{"a":571,"b":582},{"a":1095,"b":1106}]},{"name":"min_slot","required":true,"transform":{"type":"scalar"},"locs":[{"a":490,"b":499},{"a":990,"b":999}]},{"name":"max_slot","required":true,"transform":{"type":"scalar"},"locs":[{"a":525,"b":534},{"a":1037,"b":1046}]},{"name":"limit","required":true,"transform":{"type":"scalar"},"locs":[{"a":1126,"b":1132}]}],"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 \"Block\".slot as action_slot\nFROM \"AssetMint\"\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!\n AND \"Block\".height <= (\n SELECT MAX(\"Heights\".height) FROM\n (SELECT \"Block\".height as height FROM \"AssetMint\"\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\n WHERE\n \"Block\".slot > :min_slot!\n AND \"Block\".slot <= :max_slot!\n AND \"NativeAsset\".policy_id IN :policy_ids!\n LIMIT :limit!) AS \"Heights\"\n )\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,
* "Block".slot as action_slot
* FROM "AssetMint"
* 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!
* AND "Block".height <= (
* SELECT MAX("Heights".height) FROM
* (SELECT "Block".height as height FROM "AssetMint"
* 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!
* LIMIT :limit!) AS "Heights"
* )
* ORDER BY ("Block".height, "Transaction".tx_index) ASC
* ```
*/
export const sqlMintRangeByPolicyIds = new PreparedQuery<ISqlMintRangeByPolicyIdsParams,ISqlMintRangeByPolicyIdsResult>(sqlMintRangeByPolicyIdsIR);


Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,56 @@ SELECT
"AssetMint".amount as amount,
encode("NativeAsset".policy_id, 'hex') as policy_id,
encode("NativeAsset".asset_name, 'hex') as asset_name,
"Cip25Entry".payload as cip25_data,
encode("Transaction".hash, 'hex') as action_tx_id,
"Block".slot as action_slot
FROM "AssetMint"
JOIN "NativeAsset" ON "NativeAsset".id = "AssetMint".asset_id
LEFT JOIN "TransactionMetadata" ON "TransactionMetadata".tx_id = "NativeAsset".first_tx
LEFT JOIN "Cip25Entry" ON
"Cip25Entry".asset_id = "NativeAsset".id
AND "Cip25Entry".metadata_id = "TransactionMetadata".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 "Block".height <= (
SELECT MAX("Heights".height) FROM
(SELECT "Block".height as height FROM "AssetMint"
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!
LIMIT :limit!) AS "Heights"
)
ORDER BY ("Block".height, "Transaction".tx_index) ASC;

/*
@name sqlMintRangeByPolicyIds
@param policy_ids -> (...)
*/
SELECT
"AssetMint".amount as amount,
encode("NativeAsset".policy_id, 'hex') as policy_id,
encode("NativeAsset".asset_name, 'hex') as asset_name,
"Cip25Entry".payload as cip25_data,
encode("Transaction".hash, 'hex') as action_tx_id,
"Block".slot as action_slot
FROM "AssetMint"
JOIN "NativeAsset" ON "NativeAsset".id = "AssetMint".asset_id
LEFT JOIN "TransactionMetadata" ON "TransactionMetadata".tx_id = "NativeAsset".first_tx
LEFT JOIN "Cip25Entry" ON
"Cip25Entry".asset_id = "NativeAsset".id
AND "Cip25Entry".metadata_id = "TransactionMetadata".id;
JOIN "Transaction" ON "Transaction".id = "AssetMint".tx_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!
AND "Block".height <= (
SELECT MAX("Heights".height) FROM
(SELECT "Block".height as height FROM "AssetMint"
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!
LIMIT :limit!) AS "Heights"
)
ORDER BY ("Block".height, "Transaction".tx_index) ASC;
22 changes: 12 additions & 10 deletions webserver/server/app/services/MintRangeService.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
import type { PoolClient } from 'pg';
import type { ISqlMintRangeResult} from '../models/assets/mintRange.queries';
import { sqlMintRange } from '../models/assets/mintRange.queries';
import type { ISqlMintRangeResult, ISqlMintRangeByPolicyIdsResult } from '../models/asset/sqlMintRange.queries';
import { sqlMintRange, sqlMintRangeByPolicyIds } from '../models/asset/sqlMintRange.queries';
import type { PolicyId } from "../../../shared/models/PolicyIdAssetMap";

export async function mintRange(request: {
range: { minSlot: number, maxSlot: number },
params: { afterSlot: number, untilSlot: number, limit: number },
dbTx: PoolClient,
}): Promise<ISqlMintRangeResult[]> {
return (await sqlMintRange.run({
min_slot: request.range.minSlot,
max_slot: request.range.maxSlot,
min_slot: request.params.afterSlot,
max_slot: request.params.untilSlot,
limit: request.params.limit.toString(),
}, request.dbTx));
}

export async function mintRangeByPolicyIds(request: {
range: { minSlot: number, maxSlot: number },
params: { afterSlot: number, untilSlot: number, limit: number },
policyIds: PolicyId[],
dbTx: PoolClient,
}): Promise<ISqlMintRangeResult[]> {
}): Promise<ISqlMintRangeByPolicyIdsResult[]> {
return (await sqlMintRangeByPolicyIds.run({
min_slot: request.range.minSlot,
max_slot: request.range.maxSlot,
policyIds: request.policyIds
min_slot: request.params.afterSlot,
max_slot: request.params.untilSlot,
limit: request.params.limit.toString(),
policy_ids: request.policyIds.map(id => Buffer.from(id, 'hex')),
}, request.dbTx));
}
4 changes: 4 additions & 0 deletions webserver/shared/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,8 @@ export const POOL_DELEGATION_LIMIT = {

export const ASSET_UTXOS_LIMIT = {
ASSETS: 50,
};

export const MINT_BURN_LIMIT = {
MAX_LIMIT: 100,
};
Loading

0 comments on commit 3a9e9d0

Please sign in to comment.