Skip to content

Commit

Permalink
feat(governance): add new endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
1000101 committed Jan 21, 2024
1 parent 748ccba commit 400a463
Show file tree
Hide file tree
Showing 24 changed files with 583 additions and 31 deletions.
Binary file not shown.
Binary file not shown.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"dependencies": {
"@blockfrost/blockfrost-js": "5.4.0",
"@blockfrost/blockfrost-utils": "2.8.0",
"@blockfrost/openapi": "0.1.61-beta.15",
"@blockfrost/openapi": "0.1.61-beta.22",
"@emurgo/cardano-serialization-lib-nodejs": "11.5.0",
"@emurgo/cip14-js": "^3.0.1",
"@fastify/cors": "^8.3.0",
Expand Down
6 changes: 6 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ const start = (options = {}): FastifyInstance => {
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'));
registerRoute(app, import('./routes/governance/dreps/drep-id/votes.js'));
registerRoute(app, import('./routes/governance/proposals/index.js'));
registerRoute(app, import('./routes/governance/proposals/tx-hash/cert-index/index.js'));
registerRoute(app, import('./routes/governance/proposals/tx-hash/cert-index/votes.js'));
registerRoute(app, import('./routes/governance/votes.js'));
}

// health
Expand Down Expand Up @@ -158,6 +163,7 @@ const start = (options = {}): FastifyInstance => {
registerRoute(app, import('./routes/pools/pool-id/metadata.js'));
registerRoute(app, import('./routes/pools/pool-id/relays.js'));
registerRoute(app, import('./routes/pools/pool-id/updates.js'));
registerRoute(app, import('./routes/pools/pool-id/votes.js'));

// root
registerRoute(app, import('./routes/root/index.js'));
Expand Down
34 changes: 34 additions & 0 deletions src/routes/governance/dreps/drep-id/votes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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/votes',
method: 'GET',
schema: getSchemaForEndpoint('/governance/dreps/{drep_id}/votes'),

handler: async (request: FastifyRequest<QueryTypes.RequestParametersDRepID>, reply) => {
const clientDbSync = await getDbSync(fastify);

const { rows }: { rows: ResponseTypes.DRepsDrepIDVotes } =
await clientDbSync.query<QueryTypes.DRepsDrepIDVotes>(
SQLQuery.get('governance_dreps_drep_id_votes'),
[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;
34 changes: 34 additions & 0 deletions src/routes/governance/proposals/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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/proposals',
method: 'GET',
schema: getSchemaForEndpoint('/governance/proposals'),

handler: async (request: FastifyRequest<QueryTypes.RequestParametersDRepID>, reply) => {
const clientDbSync = await getDbSync(fastify);

const { rows }: { rows: ResponseTypes.DRepsDrepIDVotes } =
await clientDbSync.query<QueryTypes.DRepsDrepIDVotes>(
SQLQuery.get('governance_proposals'),
[request.query.order, request.query.count, request.query.page],
);

clientDbSync.release();

if (rows.length === 0) {
return reply.send([]);
}

return reply.send(rows);
},
});
}

export default route;
35 changes: 35 additions & 0 deletions src/routes/governance/proposals/tx-hash/cert-index/index.ts
Original file line number Diff line number Diff line change
@@ -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';
import { handle404 } from '../../../../../utils/error-handler.js';

async function route(fastify: FastifyInstance) {
fastify.route({
url: '/governance/proposals/:tx_hash/:cert_index',
method: 'GET',
schema: getSchemaForEndpoint('/governance/proposals/{tx_hash}/{cert_index}'),

handler: async (request: FastifyRequest<QueryTypes.RequestParametersProposal>, reply) => {
const clientDbSync = await getDbSync(fastify);

const { rows }: { rows: ResponseTypes.ProposalsProposal[] } =
await clientDbSync.query<QueryTypes.ProposalsProposal>(
SQLQuery.get('governance_proposals_proposal'),
[request.params.tx_hash, request.params.cert_index],
);

clientDbSync.release();
const row = rows[0];

if (!row) {
return handle404(reply);
}
return reply.send(row);
},
});
}

export default route;
40 changes: 40 additions & 0 deletions src/routes/governance/proposals/tx-hash/cert-index/votes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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/proposals/:tx_hash/:cert_index/votes',
method: 'GET',
schema: getSchemaForEndpoint('/governance/proposals/{tx_hash}/{cert_index}/votes'),

handler: async (request: FastifyRequest<QueryTypes.RequestParametersProposalVotes>, reply) => {
const clientDbSync = await getDbSync(fastify);

const { rows }: { rows: ResponseTypes.ProposalsProposalVote } =
await clientDbSync.query<QueryTypes.ProposalsProposalVote>(
SQLQuery.get('governance_proposals_proposal_votes'),
[
request.query.order,
request.query.count,
request.query.page,
request.params.tx_hash,
request.params.cert_index,
],
);

clientDbSync.release();

if (rows.length === 0) {
return reply.send([]);
}

return reply.send(rows);
},
});
}

export default route;
35 changes: 35 additions & 0 deletions src/routes/governance/votes.ts
Original file line number Diff line number Diff line change
@@ -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/votes',
method: 'GET',
schema: getSchemaForEndpoint('/governance/votes'),

handler: async (request: FastifyRequest<QueryTypes.RequestParametersDRepID>, reply) => {
const clientDbSync = await getDbSync(fastify);

const { rows }: { rows: ResponseTypes.DRepsDrepIDVotes } =
await clientDbSync.query<QueryTypes.DRepsDrepIDVotes>(SQLQuery.get('governance_votes'), [
request.query.order,
request.query.count,
request.query.page,
]);

clientDbSync.release();

if (rows.length === 0) {
return reply.send([]);
}

return reply.send(rows);
},
});
}

export default route;
77 changes: 77 additions & 0 deletions src/routes/pools/pool-id/votes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { FastifyInstance, FastifyRequest } from 'fastify';
import { getSchemaForEndpoint } from '@blockfrost/openapi';
import { isUnpaged } from '../../../utils/routes.js';
import { toJSONStream } from '../../../utils/string-utils.js';
import { SQLQuery } from '../../../sql/index.js';
import * as QueryTypes from '../../../types/queries/pools.js';
import * as ResponseTypes from '../../../types/responses/pools.js';
import { getDbSync } from '../../../utils/database.js';
import { handle400Custom, handle404 } from '../../../utils/error-handler.js';
import { validateAndConvertPool } from '../../../utils/validation.js';

async function route(fastify: FastifyInstance) {
fastify.route({
url: '/pools/:pool_id/votes',
method: 'GET',
schema: getSchemaForEndpoint('/pools/{pool_id}/votes'),
handler: async (request: FastifyRequest<QueryTypes.RequestParameters>, reply) => {
const clientDbSync = await getDbSync(fastify);

try {
// validate (and convert hex->bech32 if needed) pool ID
const pool_id = validateAndConvertPool(request.params.pool_id);

if (!pool_id) {
clientDbSync.release();
return handle400Custom(reply, 'Invalid or malformed pool id format.');
}

const query404_pool = await clientDbSync.query<QueryTypes.ResultFound>(
SQLQuery.get('pools_404'),
[pool_id],
);

if (query404_pool.rows.length === 0) {
clientDbSync.release();
return handle404(reply);
}

const unpaged = isUnpaged(request);
const { rows }: { rows: ResponseTypes.PoolVotes } = unpaged
? await clientDbSync.query<QueryTypes.PoolVotes>(
SQLQuery.get('pools_pool_id_updates_unpaged'),
[request.query.order, pool_id],
)
: await clientDbSync.query<QueryTypes.PoolVotes>(SQLQuery.get('pools_pool_id_votes'), [
request.query.order,
request.query.count,
request.query.page,
pool_id,
]);

clientDbSync.release();

if (rows.length === 0) {
return reply.send([]);
}

if (unpaged) {
// Use of Reply.raw functions is at your own risk as you are skipping all the Fastify logic of handling the HTTP response
// https://www.fastify.io/docs/latest/Reference/Reply/#raw
reply.raw.writeHead(200, { 'Content-Type': 'application/json' });
await toJSONStream(rows, reply.raw);
return reply;
} else {
return reply.send(rows);
}
} catch (error) {
if (clientDbSync) {
clientDbSync.release();
}
throw error;
}
},
});
}

export default route;
29 changes: 29 additions & 0 deletions src/sql/governance/dreps_drep_id_votes.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
SELECT encode(tx.hash, 'hex') AS "tx_hash",
vp.index AS "cert_index",
LOWER(vote::TEXT) AS "vote" -- Yes, No, Abstain -> yes,no,abstain
FROM voting_procedure vp
JOIN drep_hash dh ON (vp.drep_voter = dh.id)
JOIN tx ON (vp.tx_id = tx.id)
WHERE dh.view = $4
ORDER BY CASE
WHEN LOWER($1) = 'desc' THEN vp.id
END DESC,
CASE
WHEN LOWER($1) <> 'desc'
OR $1 IS NULL THEN vp.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
31 changes: 31 additions & 0 deletions src/sql/governance/proposals.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
SELECT encode(tx.hash, 'hex') AS "tx_hash",
ga.index AS "cert_index",
(
LOWER(
regexp_replace(type::TEXT, '(?<=.{1})([A-Z])', '_\1', 'g')
)
) AS "governance_type" -- type HardForkInitiation, NewCommittee, NewConstitution, InfoAction, NoConfidence, ParameterChange, TreasuryWithdrawals
FROM governance_action ga
JOIN tx ON (ga.tx_id = tx.id)
ORDER BY CASE
WHEN LOWER($1) = 'desc' THEN ga.id
END DESC,
CASE
WHEN LOWER($1) <> 'desc'
OR $1 IS NULL THEN ga.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
24 changes: 24 additions & 0 deletions src/sql/governance/proposals_proposal.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
SELECT encode(tx.hash, 'hex') AS "tx_hash",
ga.index AS "cert_index",
(
LOWER(
regexp_replace(type::TEXT, '(?<=.{1})([A-Z])', '_\1', 'g')
)
) AS "governance_type",
-- type HardForkInitiation, NewCommittee, NewConstitution, InfoAction, NoConfidence, ParameterChange, TreasuryWithdrawals
ga.description AS "governance_description",
ga.deposit::TEXT AS "deposit",
sa.view AS "return_address",
ga.ratified_epoch AS "ratified_epoch",
ga.enacted_epoch AS "enacted_epoch",
ga.dropped_epoch AS "dropped_epoch",
ga.expired_epoch AS "expired_epoch",
ga.expiration AS "expiration",
va.url AS "anchor_url",
encode(va.data_hash, 'hex') AS "anchor_hash"
FROM governance_action ga
JOIN tx ON (ga.tx_id = tx.id)
JOIN stake_address sa ON (ga.return_address = sa.id)
LEFT JOIN voting_anchor va ON (va.id = ga.voting_anchor_id)
WHERE encode(tx.hash, 'hex') = $1
AND ga.index = $2
Loading

0 comments on commit 400a463

Please sign in to comment.