diff --git a/src/app.ts b/src/app.ts index a591492a..f1fa2388 100644 --- a/src/app.ts +++ b/src/app.ts @@ -118,9 +118,12 @@ const start = (options = {}): FastifyInstance => { if (isCIP1694Active) { // governance registerRoute(app, import('./routes/governance/dreps/index.js')); - registerRoute(app, import('./routes/governance/dreps/hash/index.js')); - registerRoute(app, import('./routes/governance/dreps/hash/distribution.js')); + registerRoute(app, import('./routes/governance/dreps/drep-id/index.js')); + registerRoute(app, import('./routes/governance/dreps/drep-id/delegators.js')); + registerRoute(app, import('./routes/governance/dreps/drep-id/metadata.js')); + registerRoute(app, import('./routes/governance/dreps/drep-id/updates.js')); } + // health registerRoute(app, import('./routes/health/index.js')); registerRoute(app, import('./routes/health/clock.js')); diff --git a/src/routes/governance/dreps/drep-id/delegators.ts b/src/routes/governance/dreps/drep-id/delegators.ts new file mode 100644 index 00000000..9246212f --- /dev/null +++ b/src/routes/governance/dreps/drep-id/delegators.ts @@ -0,0 +1,35 @@ +import { FastifyInstance, FastifyRequest } from 'fastify'; +import * as QueryTypes from '../../../../types/queries/governance.js'; +import * as ResponseTypes from '../../../../types/responses/governance.js'; +import { getDbSync } from '../../../../utils/database.js'; +import { SQLQuery } from '../../../../sql/index.js'; +//import { getSchemaForEndpoint } from '@blockfrost/openapi'; + +async function route(fastify: FastifyInstance) { + fastify.route({ + url: '/governance/dreps/:drep_id/delegators', + method: 'GET', + // TODO: SCHEMA + // schema: getSchemaForEndpoint('/governance/dreps/{drep_id}/delegators'), + + handler: async (request: FastifyRequest, reply) => { + const clientDbSync = await getDbSync(fastify); + + const { rows }: { rows: ResponseTypes.DRepsDrepIDDelegators } = + await clientDbSync.query( + SQLQuery.get('governance_dreps_drep_id_delegators'), + [request.query.order, request.query.count, request.query.page, request.params.drep_id], + ); + + clientDbSync.release(); + + if (rows.length === 0) { + return reply.send([]); + } + + return reply.send(rows); + }, + }); +} + +export default route; diff --git a/src/routes/governance/dreps/hash/index.ts b/src/routes/governance/dreps/drep-id/index.ts similarity index 77% rename from src/routes/governance/dreps/hash/index.ts rename to src/routes/governance/dreps/drep-id/index.ts index d2eb284d..4977068b 100644 --- a/src/routes/governance/dreps/hash/index.ts +++ b/src/routes/governance/dreps/drep-id/index.ts @@ -4,14 +4,15 @@ import * as ResponseTypes from '../../../../types/responses/governance.js'; import { getDbSync } from '../../../../utils/database.js'; import { handle404 } from '../../../../utils/error-handler.js'; import { SQLQuery } from '../../../../sql/index.js'; +//import { getSchemaForEndpoint } from '@blockfrost/openapi'; async function route(fastify: FastifyInstance) { fastify.route({ - url: '/governance/dreps/:hash', + url: '/governance/dreps/:drep_id', method: 'GET', - // TODO: add schema when available - // schema: getSchemaForEndpoint('/governance/dreps/{hash}'), - handler: async (request: FastifyRequest, reply) => { + // TODO: SCHEMA + //schema: getSchemaForEndpoint('/governance/dreps/{drep_id}'), + handler: async (request: FastifyRequest, reply) => { const clientDbSync = await getDbSync(fastify); const { rows }: { rows: ResponseTypes.DRepsDrepID } = @@ -20,7 +21,6 @@ async function route(fastify: FastifyInstance) { ]); clientDbSync.release(); - const row = rows[0]; if (!row) { diff --git a/src/routes/governance/dreps/drep-id/metadata.ts b/src/routes/governance/dreps/drep-id/metadata.ts new file mode 100644 index 00000000..2c4a1bf4 --- /dev/null +++ b/src/routes/governance/dreps/drep-id/metadata.ts @@ -0,0 +1,36 @@ +import { FastifyInstance, FastifyRequest } from 'fastify'; +import * as QueryTypes from '../../../../types/queries/governance.js'; +import * as ResponseTypes from '../../../../types/responses/governance.js'; +import { getDbSync } from '../../../../utils/database.js'; +import { handle404 } from '../../../../utils/error-handler.js'; +import { SQLQuery } from '../../../../sql/index.js'; +//import { getSchemaForEndpoint } from '@blockfrost/openapi'; + +async function route(fastify: FastifyInstance) { + fastify.route({ + url: '/governance/dreps/:drep_id/metadata', + method: 'GET', + // TODO: SCHEMA + // schema: getSchemaForEndpoint('/governance/dreps/{drep_id}/metadata'), + handler: async (request: FastifyRequest, reply) => { + const clientDbSync = await getDbSync(fastify); + + const { rows }: { rows: ResponseTypes.DRepsDrepIDMetadata } = + await clientDbSync.query( + SQLQuery.get('governance_dreps_drep_id_metadata'), + [request.params.drep_id], + ); + + clientDbSync.release(); + + const row = rows[0]; + + if (!row) { + return handle404(reply); + } + return reply.send(row); + }, + }); +} + +export default route; diff --git a/src/routes/governance/dreps/drep-id/updates.ts b/src/routes/governance/dreps/drep-id/updates.ts new file mode 100644 index 00000000..3e13ca1a --- /dev/null +++ b/src/routes/governance/dreps/drep-id/updates.ts @@ -0,0 +1,35 @@ +import { FastifyInstance, FastifyRequest } from 'fastify'; +import * as QueryTypes from '../../../../types/queries/governance.js'; +import * as ResponseTypes from '../../../../types/responses/governance.js'; +import { getDbSync } from '../../../../utils/database.js'; +import { SQLQuery } from '../../../../sql/index.js'; +//import { getSchemaForEndpoint } from '@blockfrost/openapi'; + +async function route(fastify: FastifyInstance) { + fastify.route({ + url: '/governance/dreps/:drep_id/updates', + method: 'GET', + // TODO: SCHEMA + // schema: getSchemaForEndpoint('/governance/dreps/{drep_id}/updates'), + + handler: async (request: FastifyRequest, reply) => { + const clientDbSync = await getDbSync(fastify); + + const { rows }: { rows: ResponseTypes.DRepsDrepIDUpdates } = + await clientDbSync.query( + SQLQuery.get('governance_dreps_drep_id_updates'), + [request.query.order, request.query.count, request.query.page, request.params.drep_id], + ); + + clientDbSync.release(); + + if (rows.length === 0) { + return reply.send([]); + } + + return reply.send(rows); + }, + }); +} + +export default route; diff --git a/src/sql/governance/dreps.sql b/src/sql/governance/dreps.sql index 0b84f489..9012b937 100644 --- a/src/sql/governance/dreps.sql +++ b/src/sql/governance/dreps.sql @@ -1 +1,25 @@ -governance_dreps \ No newline at end of file +SELECT dh.view AS "drep_id", + encode(dh.raw, 'hex') AS "hex" +FROM drep_hash dh +ORDER BY CASE + WHEN LOWER($1) = 'desc' THEN dh.id + END DESC, + CASE + WHEN LOWER($1) <> 'desc' + OR $1 IS NULL THEN dh.id + END ASC +LIMIT CASE + WHEN $2 >= 1 + AND $2 <= 100 THEN $2 + ELSE 100 + END OFFSET CASE + WHEN $3 > 1 + AND $3 < 2147483647 THEN ($3 - 1) * ( + CASE + WHEN $2 >= 1 + AND $2 <= 100 THEN $2 + ELSE 100 + END + ) + ELSE 0 + END \ No newline at end of file diff --git a/src/sql/governance/dreps_drep_id.sql b/src/sql/governance/dreps_drep_id.sql new file mode 100644 index 00000000..f13bd799 --- /dev/null +++ b/src/sql/governance/dreps_drep_id.sql @@ -0,0 +1,42 @@ +WITH queried_epoch AS ( + SELECT no AS "epoch_no" + FROM epoch e + ORDER BY e.no DESC + LIMIT 1 +) +SELECT dh.view AS "drep_id", + encode(dh.raw, 'hex') AS "hex", + COALESCE(dd.amount, 0)::TEXT AS "amount", + ( + CASE + WHEN dr.deposit >= 0 THEN true + ELSE false + END + ) AS "is_registered", + ( + CASE + WHEN dr.deposit >= 0 THEN ( + SELECT b.epoch_no + FROM block b + WHERE b.id = ( + SELECT tx.block_id + FROM tx + WHERE tx.id = (dr.tx_id) + ) + ) + ELSE NULL + END + ) AS "active_epoch", + dh.has_script AS "has_script" +FROM drep_hash dh + JOIN drep_registration dr ON (dh.id = dr.drep_hash_id) + LEFT JOIN drep_distr dd ON ( + dh.id = dd.hash_id + AND dd.epoch_no = ( + SELECT * + FROM queried_epoch + ) + ) +WHERE dh.view = $1 +ORDER BY (tx_id, cert_index) DESC +LIMIT 1 \ No newline at end of file diff --git a/src/sql/governance/dreps_drep_id_delegators.sql b/src/sql/governance/dreps_drep_id_delegators.sql new file mode 100644 index 00000000..e6f35517 --- /dev/null +++ b/src/sql/governance/dreps_drep_id_delegators.sql @@ -0,0 +1,79 @@ +WITH current_epoch AS ( + SELECT b.epoch_no + FROM block b + ORDER BY b.id DESC + LIMIT 1 +) +SELECT "address" AS "address", + ( + ( + SELECT COALESCE(SUM(txo.value), 0) + FROM tx_out txo + LEFT JOIN tx_in txi ON (txo.tx_id = txi.tx_out_id) + AND (txo.index = txi.tx_out_index) + WHERE txi IS NULL + AND txo.stake_address_id = address_id + ) + ( + SELECT COALESCE(SUM(amount), 0) + FROM reward r + WHERE (r.addr_id = address_id) + AND r.spendable_epoch <= ( + SELECT * + FROM current_epoch + ) + ) - ( + SELECT COALESCE(SUM(amount), 0) + FROM withdrawal w + WHERE (w.addr_id = address_id) + ) + )::TEXT AS "amount" -- cast to TEXT to avoid number overflow +FROM ( + SELECT sa.view AS "address", + dv.addr_id AS "address_id", + dv.id AS "did" + FROM delegation_vote dv + JOIN drep_hash dh ON (dh.id = dv.drep_hash_id) + JOIN stake_address sa ON (sa.id = dv.addr_id) + WHERE dh.view = $4 + AND dv.id = ( + SELECT MAX(id) + FROM delegation_vote + WHERE addr_id = dv.addr_id + ) + GROUP BY sa.view, + dv.drep_hash_id, + dv.addr_id, + dv.id + ORDER BY CASE + WHEN LOWER($1) = 'desc' THEN dv.id + END DESC, + CASE + WHEN LOWER($1) <> 'desc' + OR $1 IS NULL THEN dv.id + END ASC + LIMIT CASE + WHEN $2 >= 1 + AND $2 <= 100 THEN $2 + ELSE 100 + END OFFSET CASE + WHEN $3 > 1 + AND $3 < 2147483647 THEN ($3 - 1) * ( + CASE + WHEN $2 >= 1 + AND $2 <= 100 THEN $2 + ELSE 100 + END + ) + ELSE 0 + END + ) "sorted_limited" +GROUP BY address, + address_id, + did +ORDER BY CASE + WHEN LOWER($1) = 'desc' THEN did + END DESC, + CASE + WHEN LOWER($1) <> 'desc' + OR $1 IS NULL THEN did + END ASC \ No newline at end of file diff --git a/src/sql/governance/dreps_drep_id_metadata.sql b/src/sql/governance/dreps_drep_id_metadata.sql new file mode 100644 index 00000000..b468462e --- /dev/null +++ b/src/sql/governance/dreps_drep_id_metadata.sql @@ -0,0 +1,16 @@ +SELECT dh.view AS "drep_id", + encode(dh.raw, 'hex') AS "hex", + CASE + WHEN dr.deposit >= 0 THEN va.url + ELSE NULL + END AS "url", + CASE + WHEN dr.deposit >= 0 THEN encode(va.data_hash, 'hex') + ELSE NULL + END AS "hash" +FROM drep_hash dh + JOIN drep_registration dr ON (dh.id = dr.drep_hash_id) + LEFT JOIN voting_anchor va ON (dr.voting_anchor_id = va.id) +WHERE dh.view = $1 +ORDER BY (dr.tx_id, dr.cert_index) DESC +LIMIT 1 \ No newline at end of file diff --git a/src/sql/governance/dreps_drep_id_updates.sql b/src/sql/governance/dreps_drep_id_updates.sql new file mode 100644 index 00000000..3c627160 --- /dev/null +++ b/src/sql/governance/dreps_drep_id_updates.sql @@ -0,0 +1,34 @@ +SELECT encode(tx.hash, 'hex') AS "tx_hash", + dr.cert_index AS "cert_index", + ( + CASE + WHEN dr.deposit >= 0 THEN 'registered' + ELSE 'deregistered' + END + ) AS "action" +FROM drep_hash dh + JOIN drep_registration dr ON (dh.id = dr.drep_hash_id) + JOIN tx ON (dr.tx_id = tx.id) +WHERE dh.view = $4 +ORDER BY CASE + WHEN LOWER($1) = 'desc' THEN dr.id + END DESC, + CASE + WHEN LOWER($1) <> 'desc' + OR $1 IS NULL THEN dr.id + END ASC +LIMIT CASE + WHEN $2 >= 1 + AND $2 <= 100 THEN $2 + ELSE 100 + END OFFSET CASE + WHEN $3 > 1 + AND $3 < 2147483647 THEN ($3 - 1) * ( + CASE + WHEN $2 >= 1 + AND $2 <= 100 THEN $2 + ELSE 100 + END + ) + ELSE 0 + END \ No newline at end of file diff --git a/src/types/queries/governance.ts b/src/types/queries/governance.ts index 872a9d4c..bc2387ed 100644 --- a/src/types/queries/governance.ts +++ b/src/types/queries/governance.ts @@ -18,6 +18,12 @@ export interface RequestParametersDRepID { }; } +export interface RequestDRepID { + Params: { + drep_id: string; + }; +} + export interface DReps { drep_id: string; hex: string; @@ -31,3 +37,25 @@ export interface DRepsDrepID { active_epoch: number | null; has_script: boolean; } + +export interface DRepsDrepIDDelegators { + address: string; + amount: string; +} + +export interface DRepsDrepIDMetadata { + drep_id: string; + hex: string; + url: string | null; + hash: string | null; +} +export interface DRepsDrepIDUpdates { + tx_hash: string; + /** @description Certificate within the transaction */ + cert_index: number; + /** + * @description Action in the certificate + * @enum {string} + */ + action: 'registered' | 'deregistered'; +}