Skip to content

Commit

Permalink
Add WAX data support (#43)
Browse files Browse the repository at this point in the history
WAX blockchain data is stored in a different database. The API will
assume one exists for EOS and one for WAX. The `{chain}` path parameter
is added to select between the two. `/head` is renamed `/chains` and
returns information on supported Antelope chains for which data is
available.
  • Loading branch information
0237h authored Jun 27, 2024
1 parent 5d5cf46 commit dfb76a5
Show file tree
Hide file tree
Showing 7 changed files with 364 additions and 264 deletions.
16 changes: 8 additions & 8 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function AntelopeTokenAPI() {
const createUsageEndpoint = (endpoint: UsageEndpoints) => app.get(
// Hono using different syntax than OpenAPI for path parameters
// `/{path_param}` (OpenAPI) VS `/:path_param` (Hono)
endpoint.replace(/{([^}]+)}/, ":$1"),
endpoint.replace(/{([^}]+)}/g, ":$1"),
async (ctx: Context) => {
const result = EndpointByMethod["get"][endpoint].parameters.safeParse({
query: ctx.req.query(),
Expand All @@ -92,13 +92,13 @@ function AntelopeTokenAPI() {
}
);

createUsageEndpoint("/balance"); // TODO: Maybe separate `block_num`/`timestamp` queries with path parameters (additional response schemas)
createUsageEndpoint("/head");
createUsageEndpoint("/holders");
createUsageEndpoint("/supply"); // TODO: Same as `balance``
createUsageEndpoint("/tokens");
createUsageEndpoint("/transfers"); // TODO: Redefine `block_range` params
createUsageEndpoint("/transfers/{trx_id}");
createUsageEndpoint("/{chain}/balance");
createUsageEndpoint("/chains");
createUsageEndpoint("/{chain}/holders");
createUsageEndpoint("/{chain}/supply");
createUsageEndpoint("/{chain}/tokens");
createUsageEndpoint("/{chain}/transfers");
createUsageEndpoint("/{chain}/transfers/{trx_id}");

app.notFound((ctx: Context) => APIErrorResponse(ctx, 404, "route_not_found", `Path not found: ${ctx.req.method} ${ctx.req.path}`));

Expand Down
1 change: 0 additions & 1 deletion src/clickhouse/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { ping } from "./ping.js";
import { APP_NAME, config } from "../config.js";

// TODO: Check how to abort previous queries if haven't returned yet
// TODO: Make client connect to all DB instances
const client = createClient({
...config,
clickhouse_settings: {
Expand Down
2 changes: 1 addition & 1 deletion src/types/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function fixEndpointParametersCoercion() {
if (EndpointByMethod["get"][endpoint as UsageEndpoints].parameters.shape) {
Object.values(EndpointByMethod["get"][endpoint as UsageEndpoints].parameters.shape).map(p => p.shape).forEach(
// `p` can be query or path parameters
(p) => Object.keys(p).forEach(
(p) => Object.keys(p).filter(k => k !== "chain").forEach(
(key, _) => {
let zod_type = p[key] as ZodTypeAny;
let underlying_zod_type: ZodTypeAny;
Expand Down
137 changes: 78 additions & 59 deletions src/types/zod.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ export const Supply = z.object({
supply_delta: z.number(),
});

export type SupportedChains = z.infer<typeof SupportedChains>;
export const SupportedChains = z.union([z.literal("eos"), z.literal("wax")]);

export type Transfer = z.infer<typeof Transfer>;
export const Transfer = z.object({
trx_id: z.string(),
Expand All @@ -112,30 +115,10 @@ export const Version = z.object({
commit: z.string(),
});

export type get_Usage_balance = typeof get_Usage_balance;
export const get_Usage_balance = {
export type get_Usage_chains = typeof get_Usage_chains;
export const get_Usage_chains = {
method: z.literal("GET"),
path: z.literal("/balance"),
parameters: z.object({
query: z.object({
block_num: z.union([z.number(), z.undefined()]),
contract: z.union([z.string(), z.undefined()]),
symcode: z.union([z.string(), z.undefined()]),
account: z.string(),
limit: z.union([z.number(), z.undefined()]),
page: z.union([z.number(), z.undefined()]),
}),
}),
response: z.object({
data: z.array(BalanceChange),
meta: ResponseMetadata,
}),
};

export type get_Usage_head = typeof get_Usage_head;
export const get_Usage_head = {
method: z.literal("GET"),
path: z.literal("/head"),
path: z.literal("/chains"),
parameters: z.object({
query: z.object({
limit: z.number().optional(),
Expand All @@ -160,44 +143,78 @@ export const get_Monitoring_health = {
response: z.string(),
};

export type get_Monitoring_metrics = typeof get_Monitoring_metrics;
export const get_Monitoring_metrics = {
method: z.literal("GET"),
path: z.literal("/metrics"),
parameters: z.never(),
response: z.string(),
};

export type get_Docs_openapi = typeof get_Docs_openapi;
export const get_Docs_openapi = {
method: z.literal("GET"),
path: z.literal("/openapi"),
parameters: z.never(),
response: z.unknown(),
};

export type get_Docs_version = typeof get_Docs_version;
export const get_Docs_version = {
method: z.literal("GET"),
path: z.literal("/version"),
parameters: z.never(),
response: Version,
};

export type get_Usage_balance = typeof get_Usage_balance;
export const get_Usage_balance = {
method: z.literal("GET"),
path: z.literal("/{chain}/balance"),
parameters: z.object({
query: z.object({
block_num: z.union([z.number(), z.undefined()]),
contract: z.union([z.string(), z.undefined()]),
symcode: z.union([z.string(), z.undefined()]),
account: z.string(),
limit: z.union([z.number(), z.undefined()]),
page: z.union([z.number(), z.undefined()]),
}),
path: z.object({
chain: z.union([z.literal("eos"), z.literal("wax")]),
}),
}),
response: z.object({
data: z.array(BalanceChange),
meta: ResponseMetadata,
}),
};

export type get_Usage_holders = typeof get_Usage_holders;
export const get_Usage_holders = {
method: z.literal("GET"),
path: z.literal("/holders"),
path: z.literal("/{chain}/holders"),
parameters: z.object({
query: z.object({
contract: z.string(),
symcode: z.string(),
limit: z.union([z.number(), z.undefined()]),
page: z.union([z.number(), z.undefined()]),
}),
path: z.object({
chain: z.union([z.literal("eos"), z.literal("wax")]),
}),
}),
response: z.object({
data: z.array(Holder),
meta: ResponseMetadata,
}),
};

export type get_Monitoring_metrics = typeof get_Monitoring_metrics;
export const get_Monitoring_metrics = {
method: z.literal("GET"),
path: z.literal("/metrics"),
parameters: z.never(),
response: z.string(),
};

export type get_Docs_openapi = typeof get_Docs_openapi;
export const get_Docs_openapi = {
method: z.literal("GET"),
path: z.literal("/openapi"),
parameters: z.never(),
response: z.unknown(),
};

export type get_Usage_supply = typeof get_Usage_supply;
export const get_Usage_supply = {
method: z.literal("GET"),
path: z.literal("/supply"),
path: z.literal("/{chain}/supply"),
parameters: z.object({
query: z.object({
block_num: z.union([z.number(), z.undefined()]),
Expand All @@ -207,6 +224,9 @@ export const get_Usage_supply = {
limit: z.union([z.number(), z.undefined()]),
page: z.union([z.number(), z.undefined()]),
}),
path: z.object({
chain: z.union([z.literal("eos"), z.literal("wax")]),
}),
}),
response: z.object({
data: z.array(Supply),
Expand All @@ -217,12 +237,15 @@ export const get_Usage_supply = {
export type get_Usage_tokens = typeof get_Usage_tokens;
export const get_Usage_tokens = {
method: z.literal("GET"),
path: z.literal("/tokens"),
path: z.literal("/{chain}/tokens"),
parameters: z.object({
query: z.object({
limit: z.number().optional(),
page: z.number().optional(),
}),
path: z.object({
chain: z.union([z.literal("eos"), z.literal("wax")]),
}),
}),
response: z.object({
data: z.array(Supply),
Expand All @@ -233,7 +256,7 @@ export const get_Usage_tokens = {
export type get_Usage_transfers = typeof get_Usage_transfers;
export const get_Usage_transfers = {
method: z.literal("GET"),
path: z.literal("/transfers"),
path: z.literal("/{chain}/transfers"),
parameters: z.object({
query: z.object({
block_range: z.array(z.number()).optional(),
Expand All @@ -244,6 +267,9 @@ export const get_Usage_transfers = {
limit: z.number().optional(),
page: z.number().optional(),
}),
path: z.object({
chain: z.union([z.literal("eos"), z.literal("wax")]),
}),
}),
response: z.object({
data: z.array(Transfer),
Expand All @@ -254,13 +280,14 @@ export const get_Usage_transfers = {
export type get_Usage_transfer = typeof get_Usage_transfer;
export const get_Usage_transfer = {
method: z.literal("GET"),
path: z.literal("/transfers/{trx_id}"),
path: z.literal("/{chain}/transfers/{trx_id}"),
parameters: z.object({
query: z.object({
limit: z.number().optional(),
page: z.number().optional(),
}),
path: z.object({
chain: z.union([z.literal("eos"), z.literal("wax")]),
trx_id: z.string(),
}),
}),
Expand All @@ -270,28 +297,20 @@ export const get_Usage_transfer = {
}),
};

export type get_Docs_version = typeof get_Docs_version;
export const get_Docs_version = {
method: z.literal("GET"),
path: z.literal("/version"),
parameters: z.never(),
response: Version,
};

// <EndpointByMethod>
export const EndpointByMethod = {
get: {
"/balance": get_Usage_balance,
"/head": get_Usage_head,
"/chains": get_Usage_chains,
"/health": get_Monitoring_health,
"/holders": get_Usage_holders,
"/metrics": get_Monitoring_metrics,
"/openapi": get_Docs_openapi,
"/supply": get_Usage_supply,
"/tokens": get_Usage_tokens,
"/transfers": get_Usage_transfers,
"/transfers/{trx_id}": get_Usage_transfer,
"/version": get_Docs_version,
"/{chain}/balance": get_Usage_balance,
"/{chain}/holders": get_Usage_holders,
"/{chain}/supply": get_Usage_supply,
"/{chain}/tokens": get_Usage_tokens,
"/{chain}/transfers": get_Usage_transfers,
"/{chain}/transfers/{trx_id}": get_Usage_transfer,
},
};
export type EndpointByMethod = typeof EndpointByMethod;
Expand Down
36 changes: 24 additions & 12 deletions src/typespec/openapi3.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ model UsageResponse<T> {
meta: ResponseMetadata;
}

enum SupportedChains {
EOS: "eos",
WAX: "wax"
}

// Alias will *not* be present in the OpenAPI components.
// This also helps preventing self-references in generated `components` for codegen to work properly.
alias APIResponse<T> = T | APIError;
Expand All @@ -89,9 +94,10 @@ interface Usage {
@returns Array of balances.
*/
@summary("Token balance")
@route("/balance")
@route("/{chain}/balance")
@get
balance(
@path chain: SupportedChains,
@query block_num?: BlockInfo.block_num,
@query contract?: TokenIdentifier.contract,
@query symcode?: TokenIdentifier.symcode,
Expand All @@ -100,13 +106,13 @@ interface Usage {
): APIResponse<UsageResponse<BalanceChange[]>>;

/**
Information about the current head block in the database.
List of available Antelope chains and corresponding latest block for which data is available.
@returns Array of block information.
*/
@summary("Head block information")
@route("/head")
@summary("Chains and latest block available")
@route("/chains")
@get
head(...PaginationQueryParams): APIResponse<UsageResponse<{
chains(...PaginationQueryParams): APIResponse<UsageResponse<{
block_num: BlockInfo.block_num;
}[]>>;

Expand All @@ -115,9 +121,10 @@ interface Usage {
@returns Array of accounts.
*/
@summary("Token holders")
@route("/holders")
@route("/{chain}/holders")
@get
holders(
@path chain: SupportedChains,
@query contract: TokenIdentifier.contract,
@query symcode: TokenIdentifier.symcode,
...PaginationQueryParams,
Expand All @@ -128,9 +135,10 @@ interface Usage {
@returns Array of supplies.
*/
@summary("Token supply")
@route("/supply")
@route("/{chain}/supply")
@get
supply(
@path chain: SupportedChains,
@query block_num?: BlockInfo.block_num,
@query issuer?: Supply.issuer,
@query contract: TokenIdentifier.contract,
Expand All @@ -143,23 +151,26 @@ interface Usage {
@returns Array of supplies.
*/
@summary("Tokens")
@route("/tokens")
@route("/{chain}/tokens")
@get
tokens(...PaginationQueryParams): APIResponse<UsageResponse<Supply[]>>;
tokens(
@path chain: SupportedChains,
...PaginationQueryParams
): APIResponse<UsageResponse<Supply[]>>;

/**
All transfers related to a token.
@returns Array of transfers.
*/
@summary("Token transfers")
@route("/transfers")
@route("/{chain}/transfers")
@get
transfers(
@path chain: SupportedChains,
@query({
format: "csv",
})
block_range?: BlockInfo.block_num[],

@query from?: Transfer.from,
@query to?: Transfer.to,
@query contract?: TokenIdentifier.contract,
Expand All @@ -172,9 +183,10 @@ interface Usage {
@returns Array of transfers.
*/
@summary("Token transfer")
@route("/transfers/{trx_id}")
@route("/{chain}/transfers/{trx_id}")
@get
transfer(
@path chain: SupportedChains,
@path trx_id: Models.TraceInformation.trx_id,
...PaginationQueryParams,
): APIResponse<UsageResponse<Transfer[]>>;
Expand Down
Loading

0 comments on commit dfb76a5

Please sign in to comment.