Skip to content
Closed
106 changes: 106 additions & 0 deletions migrations/20240912154500_add_bns_v2_names_table.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/** @param { import("node-pg-migrate").MigrationBuilder } pgm */
exports.up = pgm => {
pgm.createTable('names_v2', {
id: {
type: 'serial',
primaryKey: true,
},
fullName: {
type: 'string',
notNull: true,
},
name: {
type: 'string',
notNull: true,
},
namespace_id: {
type: 'string',
notNull: true,
},
registered_at: {
type: 'integer',
notNull: false,
},
imported_at: {
type: 'integer',
notNull: false,
},
hashed_salted_fqn_preorder: {
type: 'string',
notNull: false,
},
preordered_by: {
type: 'string',
notNull: false,
},
renewal_height: {
type: 'integer',
notNull: true,
},
stx_burn: {
type: 'bigint',
notNull: true,
},
owner: {
type: 'string',
notNull: true,
},
tx_id: {
type: 'bytea',
notNull: true,
},
tx_index: {
type: 'smallint',
notNull: true,
},
event_index: 'integer',
status: {
type: 'string',
notNull: false,
},
canonical: {
type: 'boolean',
notNull: true,
default: true,
},
index_block_hash: {
type: 'bytea',
notNull: true,
},
parent_index_block_hash: {
type: 'bytea',
notNull: true,
},
microblock_hash: {
type: 'bytea',
notNull: true,
},
microblock_sequence: {
type: 'integer',
notNull: true,
},
microblock_canonical: {
type: 'boolean',
notNull: true,
},
});

pgm.createIndex('names_v2', 'namespace_id');
pgm.createIndex('names_v2', 'index_block_hash');
pgm.createIndex('names_v2', [
{ name: 'registered_at', sort: 'DESC' },
{ name: 'microblock_sequence', sort: 'DESC' },
{ name: 'tx_index', sort: 'DESC' },
{ name: 'event_index', sort: 'DESC' },
]);
pgm.addConstraint(
'names_v2',
'unique_name_v2_tx_id_index_block_hash_microblock_hash_event_index',
'UNIQUE(fullName, tx_id, index_block_hash, microblock_hash, event_index)'
);
pgm.addConstraint('names_v2', 'unique_fullname', 'UNIQUE(fullName)');
};

exports.down = pgm => {
pgm.dropTable('names_v2');
};
123 changes: 123 additions & 0 deletions migrations/20240912154500_add_bns_v2_namespaces_table.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/** @param { import("node-pg-migrate").MigrationBuilder } pgm */
exports.up = pgm => {
pgm.createTable('namespaces_v2', {
id: {
type: 'serial',
primaryKey: true,
},
namespace_id: {
type: 'string',
notNull: true,
},
namespace_manager: {
type: 'string',
notNull: false,
},
manager_transferable: {
type: 'boolean',
notNull: true,
},
manager_frozen: {
type: 'boolean',
notNull: true,
},
namespace_import: {
type: 'string',
notNull: true,
},
reveal_block: {
type: 'integer',
notNull: true,
},
launched_at: {
type: 'integer',
notNull: false,
},
launch_block: {
type: 'integer',
notNull: true,
},
lifetime: {
type: 'integer',
notNull: true,
},
can_update_price_function: {
type: 'boolean',
notNull: true,
},
buckets: {
type: 'string',
notNull: true,
},
base: {
type: 'numeric',
notNull: true,
},
coeff: {
type: 'numeric',
notNull: true,
},
nonalpha_discount: {
type: 'numeric',
notNull: true,
},
no_vowel_discount: {
type: 'numeric',
notNull: true,
},
status: {
type: 'string',
notNull: false,
},
tx_id: {
type: 'bytea',
notNull: true,
},
tx_index: {
type: 'smallint',
notNull: true,
},
canonical: {
type: 'boolean',
notNull: true,
default: true,
},
index_block_hash: {
type: 'bytea',
notNull: true,
},
parent_index_block_hash: {
type: 'bytea',
notNull: true,
},
microblock_hash: {
type: 'bytea',
notNull: true,
},
microblock_sequence: {
type: 'integer',
notNull: true,
},
microblock_canonical: {
type: 'boolean',
notNull: true,
},
});

pgm.createIndex('namespaces_v2', 'index_block_hash');
pgm.createIndex('namespaces_v2', [
{ name: 'launch_block', sort: 'DESC' },
{ name: 'microblock_sequence', sort: 'DESC' },
{ name: 'tx_index', sort: 'DESC' },
]);
pgm.addConstraint(
'namespaces_v2',
'unique_namespace_v2_id_tx_id_index_block_hash_microblock_hash',
'UNIQUE(namespace_id, tx_id, index_block_hash, microblock_hash)'
);
pgm.addConstraint('namespaces_v2', 'unique_namespace_id', 'UNIQUE(namespace_id)');
};

exports.down = pgm => {
pgm.dropTable('namespaces_v2');
};
10 changes: 10 additions & 0 deletions src/api/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ import FastifyMetrics from 'fastify-metrics';
import FastifyCors from '@fastify/cors';
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox';
import * as promClient from 'prom-client';
import { BnsV2NameRoutes } from './routes/bnsV2/names';
import { BnsV2NamespaceRoutes } from './routes/bnsV2/namespaces';
import { BnsV2AddressRoutes } from './routes/bnsV2/addresses';
import { BnsV2PriceRoutes } from './routes/bnsV2/pricing';
import { BnsV2ReadRoutes } from './routes/bnsV2/reads';

export interface ApiServer {
fastifyApp: FastifyInstance;
Expand Down Expand Up @@ -112,6 +117,11 @@ export const StacksApiRoutes: FastifyPluginAsync<
await fastify.register(BnsNamespaceRoutes, { prefix: '/v1/namespaces' });
await fastify.register(BnsAddressRoutes, { prefix: '/v1/addresses' });
await fastify.register(BnsPriceRoutes, { prefix: '/v2/prices' });
await fastify.register(BnsV2NameRoutes, { prefix: '/v2/names' });
await fastify.register(BnsV2NamespaceRoutes, { prefix: '/v2/namespaces' });
await fastify.register(BnsV2AddressRoutes, { prefix: '/v2/addresses' });
await fastify.register(BnsV2ReadRoutes, { prefix: '/v2/read' });
await fastify.register(BnsV2PriceRoutes, { prefix: '/v3/prices' });

await Promise.resolve();
};
Expand Down
80 changes: 80 additions & 0 deletions src/api/routes/bnsV2/addresses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { handleChainTipCache } from '../../../api/controllers/cache-controller';
import { FastifyPluginAsync } from 'fastify';
import { Type, TypeBoxTypeProvider } from '@fastify/type-provider-typebox';
import { Server } from 'node:http';
import { UnanchoredParamSchema } from '../../schemas/params';
import { InvalidRequestError, InvalidRequestErrorType } from '../../../errors';

const SUPPORTED_BLOCKCHAINS = ['stacks'];

export const BnsV2AddressRoutes: FastifyPluginAsync<
Record<never, never>,
Server,
TypeBoxTypeProvider
> = async fastify => {
fastify.get(
'/:blockchain/:address',
{
preHandler: handleChainTipCache,
schema: {
operationId: 'get_names_owned_by_address',
summary: 'Get Names Owned by Address',
description: `Retrieves a list of names owned by the address provided.`,
tags: ['Names'],
params: Type.Object({
blockchain: Type.String({
description: 'The layer-1 blockchain for the address',
examples: ['stacks'],
}),
address: Type.String({
description: 'The address to lookup',
examples: ['SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7'],
}),
}),
querystring: Type.Object({
unanchored: UnanchoredParamSchema,
}),
response: {
200: Type.Object(
{
names: Type.Array(
Type.String({
examples: ['muneeb.id'],
})
),
},
{
title: 'BnsNamesOwnByAddressResponse',
description: 'Retrieves a list of names owned by the address provided.',
}
),
},
},
},
async (req, reply) => {
const { blockchain, address } = req.params;

if (!SUPPORTED_BLOCKCHAINS.includes(blockchain)) {
throw new InvalidRequestError(
'Unsupported blockchain',
InvalidRequestErrorType.bad_request
);
}

const includeUnanchored = req.query.unanchored ?? false;

const namesV2ByAddress = await fastify.db.getNamesV2ByAddressList({
address: address,
includeUnanchored,
chainId: fastify.chainId,
});

if (namesV2ByAddress.found) {
await reply.send({ names: namesV2ByAddress.result });
} else {
await reply.send({ names: [] });
}
}
);
await Promise.resolve();
};
Loading
Loading