diff --git a/.env.example b/.env.example
index c747410..8948dc9 100644
--- a/.env.example
+++ b/.env.example
@@ -7,8 +7,7 @@ HOST=http://127.0.0.1:8123
DATABASE=default
USERNAME=default
PASSWORD=
-TABLE=
MAX_LIMIT=500
# Logging
-VERBOSE=true
+VERBOSE=true
\ No newline at end of file
diff --git a/.github/workflows/bun-test.yml b/.github/workflows/bun-test.yml
index 50ecb1e..7981b3b 100644
--- a/.github/workflows/bun-test.yml
+++ b/.github/workflows/bun-test.yml
@@ -29,4 +29,3 @@ jobs:
HOST: ${{ vars.HOST }}
USERNAME: ${{ secrets.USERNAME }}
PASSWORD: ${{ secrets.PASSWORD }}
- TABLE: ${{ secrets.TABLE }}
diff --git a/README.md b/README.md
index 85ff669..1fca386 100644
--- a/README.md
+++ b/README.md
@@ -7,20 +7,22 @@
-## REST API
+## Swagger API
### Usage
| Method | Path | Query parameters
(* = **Required**) | Description |
| :---: | --- | --- | --- |
| GET
`text/html` | `/` | - | [Swagger](https://swagger.io/) API playground |
-| GET
`application/json` | `/chains` | `limit`
`page` | Information about the chains and latest head block in the database |
-| GET
`application/json` | `/{chain}/balance` | `block_num`
`contract`
`symcode`
**`account*`**
`limit`
`page` | Balances of an account. |
-| GET
`application/json` | `/{chain}/holders` | **`contract*`**
**`symcode*`**
`limit`
`page` | List of holders of a token |
-| GET
`application/json` | `/{chain}/supply` | `block_num`
`issuer`
**`contract*`**
**`symcode*`**
`limit`
`page` | Total supply for a token |
-| GET
`application/json` | `/{chain}/tokens` | `limit`
`page` | List of available tokens |
-| GET
`application/json` | `/{chain}/transfers` | `block_range`
`from`
`to`
`contract`
`symcode`
`limit`
`page` | All transfers related to a token |
-| GET
`application/json` | `/{chain}/transfers/{trx_id}` | `limit`
`page` | Specific transfer related to a token |
+| GET
`application/json` | `/balance` | **`account*`**
`contract`
`symcode`
`limit`
`page` | Balances of an account |
+| GET
`application/json` | `/balance/historical` | **`account*`**
`block_num`
`contract`
`symcode`
`limit`
`page` | Historical token balances |
+| GET
`application/json` | `/head` | `limit`
`page` | Head block information |
+| GET
`application/json` | `/holders` | **`contract*`**
**`symcode*`**
`limit`
`page` | List of holders of a token |
+| GET
`application/json` | `/supply` | `block_num`
`issuer`
**`contract*`**
**`symcode*`**
`limit`
`page` | Total supply for a token |
+| GET
`application/json` | `/tokens` | `limit`
`page` | List of available tokens |
+| GET
`application/json` | `/transfers` | `block_range`
**`contract*`**
**`symcode*`**
`limit`
`page` | All transfers related to a token |
+| GET
`application/json` | `/transfers/account` | **`account*`**
`block_range`
`from`
`to`
`contract`
`symcode`
`limit`
`page` | All transfers related to an account |
+| GET
`application/json` | `/transfers/id` | **`trx_id*`**
`limit`
`page` | Specific transfer related to a token |
### Docs
@@ -40,12 +42,20 @@
Go to `/graphql` for a GraphIQL interface.
+### `X-Api-Key`
+
+Use the `Variables` tab at the bottom to add your API key:
+```json
+{
+ "X-Api-Key": "changeme"
+}
+```
+
### Additional notes
- For the `block_range` parameter in `transfers`, you can pass a single integer value (low bound) or an array of two values (inclusive range).
-- If you input the same account in the `from` and `to` field for transfers, you'll get all inbound and outbound transfers for that account.
-- The more parameters you add (i.e. the more precise your query is), the faster it should be for the back-end to fetch it.
-- Don't forget to request for the `meta` fields in the response to get access to pagination and statistics !
+- Use the `from` and `to` field for transfers of an account to further filter the results (i.e. incoming or outgoing transactions from/to another account).
+- Don't forget to request the `meta` fields in the response to get access to pagination and statistics !
## Requirements
@@ -158,7 +168,6 @@ HOST=http://127.0.0.1:8123
DATABASE=default
USERNAME=default
PASSWORD=
-TABLE=
MAX_LIMIT=500
# Logging
diff --git a/index.ts b/index.ts
index 91979d8..347d3d6 100644
--- a/index.ts
+++ b/index.ts
@@ -1,7 +1,7 @@
import { Hono, type Context } from "hono";
-import { type RootResolver, graphqlServer } from '@hono/graphql-server';
+import { type RootResolver, graphqlServer, getGraphQLParams } from '@hono/graphql-server';
import { buildSchema } from 'graphql';
-import { z } from 'zod';
+import { SafeParseSuccess, z } from 'zod';
import client from './src/clickhouse/client.js';
import openapi from "./static/@typespec/openapi3/openapi.json";
@@ -72,7 +72,7 @@ async function AntelopeTokenAPI() {
app.get(
"/metrics",
- async (ctx: Context) => new Response(await prometheus.registry.metrics())
+ async () => new Response(await prometheus.registry.metrics())
);
// --------------------------
@@ -95,7 +95,7 @@ async function AntelopeTokenAPI() {
ctx,
endpoint,
{
- ...path_params.data as ValidPathParams,
+ ...path_params.data as SafeParseSuccess>,
...query_params.data
} as ValidUserParams
);
@@ -112,12 +112,22 @@ async function AntelopeTokenAPI() {
// --- GraphQL endpoint ---
// ------------------------
+ // TODO: Make GraphQL endpoint use the same $SERVER parameter as Swagger if set ?
const schema = buildSchema(await Bun.file("./static/@openapi-to-graphql/graphql/schema.graphql").text());
+ const filterFields: Array = ['metrics'];
+
+ // @ts-ignore Ignore private field warning for filtering out certain operations from the schema
+ filterFields.forEach(f => delete schema._queryType._fields[f]);
+
const rootResolver: RootResolver = async (ctx?: Context) => {
if (ctx) {
+ // GraphQL resolver uses the same SQL queries backend as the REST API (`makeUsageQuery`)
const createGraphQLUsageResolver = (endpoint: UsageEndpoints) =>
- async (args: ValidUserParams) => await (await makeUsageQuery(ctx, endpoint, { ...args })).json();
+ async (args: ValidUserParams) => {
+ return await (await makeUsageQuery(ctx, endpoint, { ...args })).json();
+ };
+
return Object.keys(usageOperationsToEndpointsMap).reduce(
// SQL queries endpoints
(resolver, op) => Object.assign(
@@ -140,6 +150,10 @@ async function AntelopeTokenAPI() {
}
};
+ // TODO: Find way to log GraphQL queries (need to workaround middleware consuming Request)
+ // See: https://github.com/honojs/middleware/issues/81
+ //app.use('/graphql', async (ctx: Context) => logger.trace(await ctx.req.json()))
+
app.use(
'/graphql',
graphqlServer({
diff --git a/package.json b/package.json
index 6e6da75..d456019 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "antelope-token-api",
"description": "Token balances, supply and transfers from the Antelope blockchains",
- "version": "5.0.0",
+ "version": "6.0.0",
"homepage": "https://github.com/pinax-network/antelope-token-api",
"license": "MIT",
"authors": [
@@ -38,7 +38,7 @@
"lint": "export APP_VERSION=$(git rev-parse --short HEAD) && bun run tsc --noEmit --skipLibCheck --pretty",
"start": "export APP_VERSION=$(git rev-parse --short HEAD) && bun index.ts",
"test": "bun test --coverage",
- "types": "bun run tsp compile ./src/typespec --output-dir static && bun run openapi-to-graphql ./static/@typespec/openapi3/openapi.json --save static/@openapi-to-graphql/graphql/schema.graphql --simpleNames --singularNames && bun run kubb",
+ "types": "bun run tsp compile ./src/typespec --output-dir static && bun run openapi-to-graphql ./static/@typespec/openapi3/openapi.json --save static/@openapi-to-graphql/graphql/schema.graphql --simpleNames --singularNames --no-viewer -H 'X-Api-Key:changeme' && bun run kubb",
"types:check": "bun run tsp compile ./src/typespec --no-emit --pretty --warn-as-error",
"types:format": "bun run tsp format src/typespec/**/*.tsp",
"types:watch": "bun run tsp compile ./src/typespec --watch --pretty --warn-as-error"
diff --git a/src/types/api.ts b/src/types/api.ts
index 42b36ea..73a9a3f 100644
--- a/src/types/api.ts
+++ b/src/types/api.ts
@@ -13,22 +13,23 @@ export type UsageResponse = EndpointReturnTypes["da
export type UsageParameters = EndpointParameters;
export type ValidPathParams = EndpointParameters["path"];
-export type ValidUserParams = EndpointParameters extends { path: undefined; } ?
+export type ValidUserParams = NonNullable extends { path: undefined; } ?
// Combine path and query parameters only if path exists to prevent "never" on intersection
z.infer["query"]>
:
- z.infer["query"] & ValidPathParams>;
+ z.infer["query"] & ValidPathParams>>;
export type AdditionalQueryParams = { offset?: number; min_block?: number; max_block?: number; };
// Allow any valid parameters from the endpoint to be used as SQL query parameters
export type ValidQueryParams = ValidUserParams & AdditionalQueryParams;
-// Map stripped operations name (e.g. `Usage_transfers` stripped to `transfers`) to endpoint paths (e.g. `/{chain}/transfers`)
+// Map stripped operations name (e.g. `Usage_transfers` stripped to `transfers`) to endpoint paths (e.g. `/transfers`)
// This is used to map GraphQL operations to REST endpoints
export const usageOperationsToEndpointsMap = Object.entries(operations).filter(([k, _]) => k.startsWith("Usage")).reduce(
(o, [k, v]) => Object.assign(
o,
{
- [k.split('_')[1] as string]: Object.entries(paths).find(([k_, v_]) => v_.get === v)?.[0]
+ // Split once on first underscore to create keys (e.g. `Usage_transfersAccount` => `transfersAccount`)
+ [k.split('_')[1] as string]: Object.entries(paths).find(([_, v_]) => v_.get === v)?.[0]
}
), {}
) as { [key in string]: UsageEndpoints };
\ No newline at end of file
diff --git a/src/types/zod.gen.ts b/src/types/zod.gen.ts
index 985f833..d2e05a7 100644
--- a/src/types/zod.gen.ts
+++ b/src/types/zod.gen.ts
@@ -5,6 +5,10 @@ export const apiErrorSchema = z.object({ "status": z.union([z.literal(500), z.li
export type ApiErrorSchema = z.infer;
+export const balanceSchema = z.object({ "last_updated_block": z.coerce.number(), "contract": z.coerce.string(), "symcode": z.coerce.string(), "balance": z.coerce.number() });
+export type BalanceSchema = z.infer;
+
+
export const balanceChangeSchema = z.object({ "trx_id": z.coerce.string(), "action_index": z.coerce.number(), "contract": z.coerce.string(), "symcode": z.coerce.string(), "precision": z.coerce.number(), "amount": z.coerce.number(), "value": z.coerce.number(), "block_num": z.coerce.number(), "timestamp": z.coerce.string(), "account": z.coerce.string(), "balance": z.coerce.string(), "balance_delta": z.coerce.number() });
export type BalanceChangeSchema = z.infer;
@@ -13,6 +17,10 @@ export const holderSchema = z.object({ "account": z.coerce.string(), "balance":
export type HolderSchema = z.infer;
+export const modelsScopeSchema = z.object({ "contract": z.coerce.string(), "symcode": z.coerce.string() });
+export type ModelsScopeSchema = z.infer;
+
+
export const paginationSchema = z.object({ "next_page": z.coerce.number(), "previous_page": z.coerce.number(), "total_pages": z.coerce.number(), "total_results": z.coerce.number() });
export type PaginationSchema = z.infer;
@@ -29,10 +37,6 @@ export const supplySchema = z.object({ "trx_id": z.coerce.string(), "action_inde
export type SupplySchema = z.infer;
-export const supportedChainsSchema = z.enum(["EOS", "WAX"]);
-export type SupportedChainsSchema = z.infer;
-
-
export const transferSchema = z.object({ "trx_id": z.coerce.string(), "action_index": z.coerce.number(), "contract": z.coerce.string(), "symcode": z.coerce.string(), "precision": z.coerce.number(), "amount": z.coerce.number(), "value": z.coerce.number(), "block_num": z.coerce.number(), "timestamp": z.coerce.string(), "from": z.coerce.string(), "to": z.coerce.string(), "quantity": z.coerce.string(), "memo": z.coerce.string() });
export type TransferSchema = z.infer;
@@ -41,26 +45,64 @@ export const versionSchema = z.object({ "version": z.coerce.string().regex(new R
export type VersionSchema = z.infer;
-export const usageChainsQueryParamsSchema = z.object({ "limit": z.coerce.number().optional(), "page": z.coerce.number().optional() }).optional();
-export type UsageChainsQueryParamsSchema = z.infer;
+export const usageBalanceQueryParamsSchema = z.object({ "account": z.coerce.string(), "contract": z.coerce.string().optional(), "symcode": z.coerce.string().optional(), "limit": z.coerce.number().optional(), "page": z.coerce.number().optional() });
+export type UsageBalanceQueryParamsSchema = z.infer;
+/**
+ * @description Array of balances.
+ */
+export const usageBalance200Schema = z.object({ "data": z.array(z.lazy(() => balanceSchema)), "meta": z.lazy(() => responseMetadataSchema) });
+export type UsageBalance200Schema = z.infer;
+/**
+ * @description An unexpected error response.
+ */
+export const usageBalanceErrorSchema = z.lazy(() => apiErrorSchema);
+export type UsageBalanceErrorSchema = z.infer;
+/**
+ * @description Array of balances.
+ */
+export const usageBalanceQueryResponseSchema = z.object({ "data": z.array(z.lazy(() => balanceSchema)), "meta": z.lazy(() => responseMetadataSchema) });
+export type UsageBalanceQueryResponseSchema = z.infer;
+
+
+export const usageBalanceHistoricalQueryParamsSchema = z.object({ "account": z.coerce.string(), "block_num": z.coerce.number(), "contract": z.coerce.string().optional(), "symcode": z.coerce.string().optional(), "limit": z.coerce.number().optional(), "page": z.coerce.number().optional() });
+export type UsageBalanceHistoricalQueryParamsSchema = z.infer;
/**
- * @description Array of block information.
+ * @description Array of balances.
*/
-export const usageChains200Schema = z.object({ "data": z.array(z.object({ "chain": z.lazy(() => supportedChainsSchema), "block_num": z.coerce.number() })), "meta": z.lazy(() => responseMetadataSchema) });
-export type UsageChains200Schema = z.infer;
+export const usageBalanceHistorical200Schema = z.object({ "data": z.array(z.lazy(() => balanceChangeSchema)), "meta": z.lazy(() => responseMetadataSchema) });
+export type UsageBalanceHistorical200Schema = z.infer;
/**
* @description An unexpected error response.
*/
-export const usageChainsErrorSchema = z.lazy(() => apiErrorSchema);
-export type UsageChainsErrorSchema = z.infer;
+export const usageBalanceHistoricalErrorSchema = z.lazy(() => apiErrorSchema);
+export type UsageBalanceHistoricalErrorSchema = z.infer;
/**
- * @description Array of block information.
+ * @description Array of balances.
*/
-export const usageChainsQueryResponseSchema = z.object({ "data": z.array(z.object({ "chain": z.lazy(() => supportedChainsSchema), "block_num": z.coerce.number() })), "meta": z.lazy(() => responseMetadataSchema) });
-export type UsageChainsQueryResponseSchema = z.infer;
+export const usageBalanceHistoricalQueryResponseSchema = z.object({ "data": z.array(z.lazy(() => balanceChangeSchema)), "meta": z.lazy(() => responseMetadataSchema) });
+export type UsageBalanceHistoricalQueryResponseSchema = z.infer;
+
+
+export const usageHeadQueryParamsSchema = z.object({ "limit": z.coerce.number().optional(), "page": z.coerce.number().optional() }).optional();
+export type UsageHeadQueryParamsSchema = z.infer;
+/**
+ * @description Head block information.
+ */
+export const usageHead200Schema = z.object({ "data": z.array(z.object({ "block_num": z.coerce.number(), "block_id": z.coerce.string() })), "meta": z.lazy(() => responseMetadataSchema) });
+export type UsageHead200Schema = z.infer;
+/**
+ * @description An unexpected error response.
+ */
+export const usageHeadErrorSchema = z.lazy(() => apiErrorSchema);
+export type UsageHeadErrorSchema = z.infer;
+/**
+ * @description Head block information.
+ */
+export const usageHeadQueryResponseSchema = z.object({ "data": z.array(z.object({ "block_num": z.coerce.number(), "block_id": z.coerce.string() })), "meta": z.lazy(() => responseMetadataSchema) });
+export type UsageHeadQueryResponseSchema = z.infer;
/**
- * @description OK or APIError.
+ * @description OK or ApiError.
*/
export const monitoringHealth200Schema = z.coerce.string();
export type MonitoringHealth200Schema = z.infer;
@@ -70,11 +112,30 @@ export type MonitoringHealth200Schema = z.infer apiErrorSchema);
export type MonitoringHealthErrorSchema = z.infer;
/**
- * @description OK or APIError.
+ * @description OK or ApiError.
*/
export const monitoringHealthQueryResponseSchema = z.coerce.string();
export type MonitoringHealthQueryResponseSchema = z.infer;
+
+export const usageHoldersQueryParamsSchema = z.object({ "contract": z.coerce.string(), "symcode": z.coerce.string(), "limit": z.coerce.number().optional(), "page": z.coerce.number().optional() });
+export type UsageHoldersQueryParamsSchema = z.infer;
+/**
+ * @description Array of accounts.
+ */
+export const usageHolders200Schema = z.object({ "data": z.array(z.lazy(() => holderSchema)), "meta": z.lazy(() => responseMetadataSchema) });
+export type UsageHolders200Schema = z.infer;
+/**
+ * @description An unexpected error response.
+ */
+export const usageHoldersErrorSchema = z.lazy(() => apiErrorSchema);
+export type UsageHoldersErrorSchema = z.infer;
+/**
+ * @description Array of accounts.
+ */
+export const usageHoldersQueryResponseSchema = z.object({ "data": z.array(z.lazy(() => holderSchema)), "meta": z.lazy(() => responseMetadataSchema) });
+export type UsageHoldersQueryResponseSchema = z.infer;
+
/**
* @description Metrics as text.
*/
@@ -107,71 +168,8 @@ export type DocsOpenapiErrorSchema = z.infer;
export const docsOpenapiQueryResponseSchema = z.object({});
export type DocsOpenapiQueryResponseSchema = z.infer;
- /**
- * @description The API version and commit hash.
- */
-export const docsVersion200Schema = z.lazy(() => versionSchema);
-export type DocsVersion200Schema = z.infer;
-/**
- * @description An unexpected error response.
- */
-export const docsVersionErrorSchema = z.lazy(() => apiErrorSchema);
-export type DocsVersionErrorSchema = z.infer;
-/**
- * @description The API version and commit hash.
- */
-export const docsVersionQueryResponseSchema = z.lazy(() => versionSchema);
-export type DocsVersionQueryResponseSchema = z.infer;
-
-
-export const usageBalancePathParamsSchema = z.object({ "chain": z.lazy(() => supportedChainsSchema) });
-export type UsageBalancePathParamsSchema = z.infer;
-
- export const usageBalanceQueryParamsSchema = z.object({ "block_num": z.coerce.number().optional(), "contract": z.coerce.string().optional(), "symcode": z.coerce.string().optional(), "account": z.coerce.string(), "limit": z.coerce.number().optional(), "page": z.coerce.number().optional() });
-export type UsageBalanceQueryParamsSchema = z.infer;
-/**
- * @description Array of balances.
- */
-export const usageBalance200Schema = z.object({ "data": z.array(z.lazy(() => balanceChangeSchema)), "meta": z.lazy(() => responseMetadataSchema) });
-export type UsageBalance200Schema = z.infer;
-/**
- * @description An unexpected error response.
- */
-export const usageBalanceErrorSchema = z.lazy(() => apiErrorSchema);
-export type UsageBalanceErrorSchema = z.infer;
-/**
- * @description Array of balances.
- */
-export const usageBalanceQueryResponseSchema = z.object({ "data": z.array(z.lazy(() => balanceChangeSchema)), "meta": z.lazy(() => responseMetadataSchema) });
-export type UsageBalanceQueryResponseSchema = z.infer;
-
-
-export const usageHoldersPathParamsSchema = z.object({ "chain": z.lazy(() => supportedChainsSchema) });
-export type UsageHoldersPathParamsSchema = z.infer;
-
- export const usageHoldersQueryParamsSchema = z.object({ "contract": z.coerce.string(), "symcode": z.coerce.string(), "limit": z.coerce.number().optional(), "page": z.coerce.number().optional() });
-export type UsageHoldersQueryParamsSchema = z.infer;
-/**
- * @description Array of accounts.
- */
-export const usageHolders200Schema = z.object({ "data": z.array(z.lazy(() => holderSchema)), "meta": z.lazy(() => responseMetadataSchema) });
-export type UsageHolders200Schema = z.infer;
-/**
- * @description An unexpected error response.
- */
-export const usageHoldersErrorSchema = z.lazy(() => apiErrorSchema);
-export type UsageHoldersErrorSchema = z.infer;
-/**
- * @description Array of accounts.
- */
-export const usageHoldersQueryResponseSchema = z.object({ "data": z.array(z.lazy(() => holderSchema)), "meta": z.lazy(() => responseMetadataSchema) });
-export type UsageHoldersQueryResponseSchema = z.infer;
-
-
-export const usageSupplyPathParamsSchema = z.object({ "chain": z.lazy(() => supportedChainsSchema) });
-export type UsageSupplyPathParamsSchema = z.infer;
- export const usageSupplyQueryParamsSchema = z.object({ "block_num": z.coerce.number().optional(), "issuer": z.coerce.string().optional(), "contract": z.coerce.string(), "symcode": z.coerce.string(), "limit": z.coerce.number().optional(), "page": z.coerce.number().optional() });
+export const usageSupplyQueryParamsSchema = z.object({ "block_num": z.coerce.number().optional(), "issuer": z.coerce.string().optional(), "contract": z.coerce.string(), "symcode": z.coerce.string(), "limit": z.coerce.number().optional(), "page": z.coerce.number().optional() });
export type UsageSupplyQueryParamsSchema = z.infer;
/**
* @description Array of supplies.
@@ -190,15 +188,12 @@ export const usageSupplyQueryResponseSchema = z.object({ "data": z.array(z.lazy(
export type UsageSupplyQueryResponseSchema = z.infer;
-export const usageTokensPathParamsSchema = z.object({ "chain": z.lazy(() => supportedChainsSchema) });
-export type UsageTokensPathParamsSchema = z.infer;
-
- export const usageTokensQueryParamsSchema = z.object({ "limit": z.coerce.number().optional(), "page": z.coerce.number().optional() }).optional();
+export const usageTokensQueryParamsSchema = z.object({ "limit": z.coerce.number().optional(), "page": z.coerce.number().optional() }).optional();
export type UsageTokensQueryParamsSchema = z.infer;
/**
- * @description Array of supplies.
+ * @description Array of token identifier.
*/
-export const usageTokens200Schema = z.object({ "data": z.array(z.lazy(() => supplySchema)), "meta": z.lazy(() => responseMetadataSchema) });
+export const usageTokens200Schema = z.object({ "data": z.array(z.lazy(() => modelsScopeSchema)), "meta": z.lazy(() => responseMetadataSchema) });
export type UsageTokens200Schema = z.infer;
/**
* @description An unexpected error response.
@@ -206,16 +201,13 @@ export type UsageTokens200Schema = z.infer;
export const usageTokensErrorSchema = z.lazy(() => apiErrorSchema);
export type UsageTokensErrorSchema = z.infer;
/**
- * @description Array of supplies.
+ * @description Array of token identifier.
*/
-export const usageTokensQueryResponseSchema = z.object({ "data": z.array(z.lazy(() => supplySchema)), "meta": z.lazy(() => responseMetadataSchema) });
+export const usageTokensQueryResponseSchema = z.object({ "data": z.array(z.lazy(() => modelsScopeSchema)), "meta": z.lazy(() => responseMetadataSchema) });
export type UsageTokensQueryResponseSchema = z.infer;
-export const usageTransfersPathParamsSchema = z.object({ "chain": z.lazy(() => supportedChainsSchema) });
-export type UsageTransfersPathParamsSchema = z.infer;
-
- export const usageTransfersQueryParamsSchema = z.object({ "block_range": z.array(z.coerce.number()).optional(), "from": z.coerce.string().optional(), "to": z.coerce.string().optional(), "contract": z.coerce.string().optional(), "symcode": z.coerce.string().optional(), "limit": z.coerce.number().optional(), "page": z.coerce.number().optional() }).optional();
+export const usageTransfersQueryParamsSchema = z.object({ "block_range": z.array(z.coerce.number()).optional(), "contract": z.coerce.string(), "symcode": z.coerce.string(), "limit": z.coerce.number().optional(), "page": z.coerce.number().optional() });
export type UsageTransfersQueryParamsSchema = z.infer;
/**
* @description Array of transfers.
@@ -234,64 +226,96 @@ export const usageTransfersQueryResponseSchema = z.object({ "data": z.array(z.la
export type UsageTransfersQueryResponseSchema = z.infer;
-export const usageTransferPathParamsSchema = z.object({ "chain": z.lazy(() => supportedChainsSchema), "trx_id": z.coerce.string() });
-export type UsageTransferPathParamsSchema = z.infer;
+export const usageTransfersAccountQueryParamsSchema = z.object({ "account": z.coerce.string(), "block_range": z.array(z.coerce.number()).optional(), "from": z.coerce.string().optional(), "to": z.coerce.string().optional(), "contract": z.coerce.string().optional(), "symcode": z.coerce.string().optional(), "limit": z.coerce.number().optional(), "page": z.coerce.number().optional() });
+export type UsageTransfersAccountQueryParamsSchema = z.infer;
+/**
+ * @description Array of transfers.
+ */
+export const usageTransfersAccount200Schema = z.object({ "data": z.array(z.lazy(() => transferSchema)), "meta": z.lazy(() => responseMetadataSchema) });
+export type UsageTransfersAccount200Schema = z.infer;
+/**
+ * @description An unexpected error response.
+ */
+export const usageTransfersAccountErrorSchema = z.lazy(() => apiErrorSchema);
+export type UsageTransfersAccountErrorSchema = z.infer;
+/**
+ * @description Array of transfers.
+ */
+export const usageTransfersAccountQueryResponseSchema = z.object({ "data": z.array(z.lazy(() => transferSchema)), "meta": z.lazy(() => responseMetadataSchema) });
+export type UsageTransfersAccountQueryResponseSchema = z.infer;
+
- export const usageTransferQueryParamsSchema = z.object({ "limit": z.coerce.number().optional(), "page": z.coerce.number().optional() }).optional();
-export type UsageTransferQueryParamsSchema = z.infer;
+export const usageTransferIdQueryParamsSchema = z.object({ "trx_id": z.coerce.string(), "limit": z.coerce.number().optional(), "page": z.coerce.number().optional() });
+export type UsageTransferIdQueryParamsSchema = z.infer;
/**
* @description Array of transfers.
*/
-export const usageTransfer200Schema = z.object({ "data": z.array(z.lazy(() => transferSchema)), "meta": z.lazy(() => responseMetadataSchema) });
-export type UsageTransfer200Schema = z.infer;
+export const usageTransferId200Schema = z.object({ "data": z.array(z.lazy(() => transferSchema)), "meta": z.lazy(() => responseMetadataSchema) });
+export type UsageTransferId200Schema = z.infer;
/**
* @description An unexpected error response.
*/
-export const usageTransferErrorSchema = z.lazy(() => apiErrorSchema);
-export type UsageTransferErrorSchema = z.infer;
+export const usageTransferIdErrorSchema = z.lazy(() => apiErrorSchema);
+export type UsageTransferIdErrorSchema = z.infer;
/**
* @description Array of transfers.
*/
-export const usageTransferQueryResponseSchema = z.object({ "data": z.array(z.lazy(() => transferSchema)), "meta": z.lazy(() => responseMetadataSchema) });
-export type UsageTransferQueryResponseSchema = z.infer;
+export const usageTransferIdQueryResponseSchema = z.object({ "data": z.array(z.lazy(() => transferSchema)), "meta": z.lazy(() => responseMetadataSchema) });
+export type UsageTransferIdQueryResponseSchema = z.infer;
- export const operations = { "Usage_chains": {
+ /**
+ * @description The Api version and commit hash.
+ */
+export const docsVersion200Schema = z.lazy(() => versionSchema);
+export type DocsVersion200Schema = z.infer;
+/**
+ * @description An unexpected error response.
+ */
+export const docsVersionErrorSchema = z.lazy(() => apiErrorSchema);
+export type DocsVersionErrorSchema = z.infer;
+/**
+ * @description The Api version and commit hash.
+ */
+export const docsVersionQueryResponseSchema = z.lazy(() => versionSchema);
+export type DocsVersionQueryResponseSchema = z.infer;
+
+ export const operations = { "Usage_balance": {
request: undefined,
parameters: {
path: undefined,
- query: usageChainsQueryParamsSchema,
+ query: usageBalanceQueryParamsSchema,
header: undefined
},
responses: {
- 200: usageChainsQueryResponseSchema,
- default: usageChainsQueryResponseSchema
+ 200: usageBalanceQueryResponseSchema,
+ default: usageBalanceQueryResponseSchema
},
errors: {}
- }, "Monitoring_health": {
+ }, "Usage_balanceHistorical": {
request: undefined,
parameters: {
path: undefined,
- query: undefined,
+ query: usageBalanceHistoricalQueryParamsSchema,
header: undefined
},
responses: {
- 200: monitoringHealthQueryResponseSchema,
- default: monitoringHealthQueryResponseSchema
+ 200: usageBalanceHistoricalQueryResponseSchema,
+ default: usageBalanceHistoricalQueryResponseSchema
},
errors: {}
- }, "Monitoring_metrics": {
+ }, "Usage_head": {
request: undefined,
parameters: {
path: undefined,
- query: undefined,
+ query: usageHeadQueryParamsSchema,
header: undefined
},
responses: {
- 200: monitoringMetricsQueryResponseSchema,
- default: monitoringMetricsQueryResponseSchema
+ 200: usageHeadQueryResponseSchema,
+ default: usageHeadQueryResponseSchema
},
errors: {}
- }, "Docs_openapi": {
+ }, "Monitoring_health": {
request: undefined,
parameters: {
path: undefined,
@@ -299,50 +323,50 @@ export type UsageTransferQueryResponseSchema = z.infer;
+alias TokenIdentifier = Models.Scope;
// Models will be present in the OpenAPI components
model Transfer is Models.Transfer;
@@ -48,6 +54,11 @@ model Holder {
account: BalanceChange.account;
balance: BalanceChange.value;
}
+model Balance {
+ last_updated_block: BalanceChange.block_num,
+ ...TokenIdentifier,
+ balance: BalanceChange.value
+}
model QueryStatistics {
elapsed: float;
@@ -72,51 +83,59 @@ model UsageResponse {
meta: ResponseMetadata;
}
-enum SupportedChains {
- EOS,
- 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 | APIError;
+alias ApiResponse = T | ApiError;
alias PaginationQueryParams = {
@query limit?: uint64 = 10;
@query page?: uint64 = 1;
};
-// Helper aliases for accessing underlying properties
-alias BlockInfo = Models.BlockInfo;
-alias TokenIdentifier = Models.Scope;
-
@tag("Usage")
interface Usage {
/**
- Balances of an account.
+ Token balances of an account.
@returns Array of balances.
*/
- @summary("Token balance")
- @route("/{chain}/balance")
+ @summary("Token balances")
+ @route("/balance")
@get
+ @useAuth(ApiKeyAuth)
balance(
- @path chain: SupportedChains,
- @query block_num?: BlockInfo.block_num,
+ @query account: BalanceChange.account,
@query contract?: TokenIdentifier.contract,
@query symcode?: TokenIdentifier.symcode,
+ ...PaginationQueryParams,
+ ): ApiResponse>;
+
+ /**
+ Historical token balances of an account.
+ @returns Array of balances.
+ */
+ @summary("Historical token balances")
+ @route("/balance/historical")
+ @get
+ @useAuth(ApiKeyAuth)
+ balanceHistorical(
@query account: BalanceChange.account,
+ @query block_num: BlockInfo.block_num,
+ @query contract?: TokenIdentifier.contract,
+ @query symcode?: TokenIdentifier.symcode,
...PaginationQueryParams,
- ): APIResponse>;
+ ): ApiResponse>;
/**
- List of available Antelope chains and corresponding latest block for which data is available.
- @returns Array of block information.
+ Current head block for which data is available (can be lower than head block of the chain).
+ @returns Head block information.
*/
- @summary("Chains and latest block available")
- @route("/chains")
+ @summary("Head block information")
+ @route("/head")
@get
- chains(...PaginationQueryParams): APIResponse>;
/**
@@ -124,75 +143,88 @@ interface Usage {
@returns Array of accounts.
*/
@summary("Token holders")
- @route("/{chain}/holders")
+ @route("/holders")
@get
+ @useAuth(ApiKeyAuth)
holders(
- @path chain: SupportedChains,
@query contract: TokenIdentifier.contract,
@query symcode: TokenIdentifier.symcode,
...PaginationQueryParams,
- ): APIResponse>;
+ ): ApiResponse>;
/**
Total supply for a token.
@returns Array of supplies.
*/
@summary("Token supply")
- @route("/{chain}/supply")
+ @route("/supply")
@get
+ @useAuth(ApiKeyAuth)
supply(
- @path chain: SupportedChains,
@query block_num?: BlockInfo.block_num,
@query issuer?: Supply.issuer,
@query contract: TokenIdentifier.contract,
@query symcode: TokenIdentifier.symcode,
...PaginationQueryParams,
- ): APIResponse>;
+ ): ApiResponse>;
/**
List of available tokens.
- @returns Array of supplies.
+ @returns Array of token identifier.
*/
@summary("Tokens")
- @route("/{chain}/tokens")
+ @route("/tokens")
@get
+ @useAuth(ApiKeyAuth)
tokens(
- @path chain: SupportedChains,
...PaginationQueryParams
- ): APIResponse>;
+ ): ApiResponse>;
/**
All transfers related to a token.
@returns Array of transfers.
*/
@summary("Token transfers")
- @route("/{chain}/transfers")
+ @route("/transfers")
@get
+ @useAuth(ApiKeyAuth)
transfers(
- @path chain: SupportedChains,
- @query({
- format: "csv",
- })
- block_range?: BlockInfo.block_num[],
+ @query({ format: "csv"}) block_range?: BlockInfo.block_num[],
+ @query contract: TokenIdentifier.contract,
+ @query symcode: TokenIdentifier.symcode,
+ ...PaginationQueryParams,
+ ): ApiResponse>;
+
+ /**
+ All transfers related to an account.
+ @returns Array of transfers.
+ */
+ @summary("Token transfers from and to an account")
+ @route("/transfers/account")
+ @get
+ @useAuth(ApiKeyAuth)
+ transfersAccount(
+ @query account: BalanceChange.account,
+ @query({ format: "csv"}) block_range?: BlockInfo.block_num[],
@query from?: Transfer.from,
@query to?: Transfer.to,
@query contract?: TokenIdentifier.contract,
@query symcode?: TokenIdentifier.symcode,
...PaginationQueryParams,
- ): APIResponse>;
+ ): ApiResponse>;
/**
Specific transfer related to a token.
@returns Array of transfers.
*/
@summary("Token transfer")
- @route("/{chain}/transfers/{trx_id}")
+ @route("/transfers/id")
@get
- transfer(
- @path chain: SupportedChains,
- @path trx_id: Models.TraceInformation.trx_id,
+ @useAuth(ApiKeyAuth)
+ transferId(
+ @query trx_id: Models.TraceInformation.trx_id,
...PaginationQueryParams,
- ): APIResponse>;
+ ): ApiResponse>;
}
model Version {
@@ -212,28 +244,28 @@ interface Docs {
@summary("OpenAPI JSON spec")
@route("/openapi")
@get
- openapi(): APIResponse>;
+ openapi(): ApiResponse>;
/**
- API version and Git short commit hash.
- @returns The API version and commit hash.
+ Api version and Git short commit hash.
+ @returns The Api version and commit hash.
*/
- @summary("API version")
+ @summary("Api version")
@route("/version")
@get
- version(): APIResponse;
+ version(): ApiResponse;
}
@tag("Monitoring")
interface Monitoring {
/**
Checks database connection.
- @returns OK or APIError.
+ @returns OK or ApiError.
*/
@summary("Health check")
@route("/health")
@get
- health(): APIResponse;
+ health(): ApiResponse;
/**
Prometheus metrics.
@@ -242,5 +274,5 @@ interface Monitoring {
@summary("Prometheus metrics")
@route("/metrics")
@get
- metrics(): APIResponse;
+ metrics(): ApiResponse;
}
diff --git a/src/usage.ts b/src/usage.ts
index 32c7aa5..a6a8526 100644
--- a/src/usage.ts
+++ b/src/usage.ts
@@ -3,8 +3,14 @@ import { APIErrorResponse } from "./utils.js";
import type { Context } from "hono";
import type { AdditionalQueryParams, UsageEndpoints, UsageResponse, ValidUserParams } from "./types/api.js";
-import { config } from "./config.js";
-import { supportedChainsSchema } from "./types/zod.gen.js";
+
+/**
+ * This function creates and send the SQL queries to the ClickHouse database based on the endpoint requested.
+ *
+ * Both the REST API and GraphQL endpoint use those.
+ * `endpoint` is a valid "Usage" endpoint (e.g. not a `/version`, `/metrics`, etc. endpoint, an actual data endpoint).
+ * `user_params` is an key-value object created from the path and query parameters present in the request.
+ **/
export async function makeUsageQuery(ctx: Context, endpoint: UsageEndpoints, user_params: ValidUserParams) {
type UsageElementReturnType = UsageResponse[number];
@@ -19,7 +25,7 @@ export async function makeUsageQuery(ctx: Context, endpoint: UsageEndpoints, use
let filters = "";
// Don't add `limit` and `block_range` to WHERE clause
- for (const k of Object.keys(query_params).filter(k => k !== "limit" && k !== "block_range" && k !== "chain")) {
+ for (const k of Object.keys(query_params).filter(k => k !== "limit" && k !== "block_range")) {
const clickhouse_type = typeof query_params[k as keyof typeof query_params] === "number" ? "int" : "String";
if (k === 'symcode') // Special case to allow case-insensitive symcode input
filters += ` (${k} = upper({${k}: ${clickhouse_type}})) AND`;
@@ -33,37 +39,11 @@ export async function makeUsageQuery(ctx: Context, endpoint: UsageEndpoints, use
let query = "";
let additional_query_params: AdditionalQueryParams = {};
- let database = config.database;
-
- if (endpoint !== "/chains") {
- const q = query_params as ValidUserParams;
- database = `${q.chain.toLowerCase()}_tokens_v1`;
- }
-
- if (endpoint == "/{chain}/balance" || endpoint == "/{chain}/supply") {
- // Need to narrow the type of `query_params` explicitly to access properties based on endpoint value
- // See https://github.com/microsoft/TypeScript/issues/33014
- const q = query_params as ValidUserParams;
- query +=
- `SELECT *`
- + ` FROM ${endpoint == "/{chain}/balance" ?
- `${database}.${q.block_num ? 'historical_' : ''}account_balances`
- : `${database}.${q.block_num ? 'historical_' : ''}token_supplies`}`
- + ` ${filters}`;
- } else if (endpoint == "/{chain}/transfers") {
- query += `SELECT * FROM `;
+ // Parse block range for endpoints that uses it. Check for single value or two comma-separated values.
+ if (endpoint == "/transfers" || endpoint == "/transfers/account") {
const q = query_params as ValidUserParams;
- // Find all incoming and outgoing transfers from single account
- if (q.from && q.to && q.from === q.to)
- filters = filters.replace(
- "(from = {from: String}) AND (to = {to: String})",
- "((from = {from: String}) OR (to = {to: String}))",
- );
-
if (q.block_range) {
- query += `${database}.transfers_block_num`;
-
if (q.block_range[0] && q.block_range[1]) {
filters +=
`${filters.length ? "AND" : "WHERE"}` +
@@ -77,30 +57,51 @@ export async function makeUsageQuery(ctx: Context, endpoint: UsageEndpoints, use
` (block_num >= {min_block: int})`;
additional_query_params.min_block = q.block_range[0];
}
- } else if (q.from) {
- query += `${database}.transfers_from`;
- } else if (q.to) {
- query += `${database}.transfers_to`;
- } else if (q.contract || q.symcode) {
- query += `${database}.transfers_contract`;
- } else {
- query += `${database}.transfers_block_num`;
}
+ }
+
+ if (endpoint == "/balance") {
+ query +=
+ `SELECT block_num AS last_updated_block, contract, symcode, value as balance FROM token_holders FINAL`
+ + ` ${filters} ORDER BY value DESC`
+ } else if (endpoint == "/balance/historical") {
+ query +=
+ `SELECT * FROM historical_account_balances`
+ + ` ${filters} ORDER BY value DESC`
+ } else if (endpoint == "/supply") {
+ // Need to narrow the type of `query_params` explicitly to access properties based on endpoint value
+ // See https://github.com/microsoft/TypeScript/issues/33014
+ const q = query_params as ValidUserParams;
+ query +=
+ `SELECT * FROM ${q.block_num ? 'historical_' : ''}token_supplies`
+ +` ${filters} ORDER BY block_num DESC`;
+ } else if (endpoint == "/transfers") {
+ query += `SELECT * FROM `;
+
+ const q = query_params as ValidUserParams;
+ if (q.contract && q.symcode)
+ query += `transfers_contract`;
+ else
+ query += `transfers_block_num`;
query += ` ${filters} ORDER BY block_num DESC`;
- } else if (endpoint == "/{chain}/holders") {
- query += `SELECT account, value AS balance FROM ${database}.token_holders FINAL ${filters} ORDER BY value DESC`;
- } else if (endpoint == "/chains") {
- for (const chain of supportedChainsSchema._def.values)
- query +=
- `SELECT '${chain}' as chain, MAX(block_num) as block_num`
- + ` FROM ${chain.toLowerCase()}_tokens_v1.cursors GROUP BY id`
- + ` UNION ALL `;
- query = query.substring(0, query.lastIndexOf(' UNION')); // Remove last item ` UNION`
- } else if (endpoint == "/{chain}/transfers/{trx_id}") {
- query += `SELECT * FROM ${database}.transfer_events ${filters} ORDER BY action_index`;
- } else if (endpoint == "/{chain}/tokens") {
- query += `SELECT * FROM ${database}.token_supplies FINAL ${filters} ORDER BY block_num DESC`;
+ } else if (endpoint == "/transfers/account") {
+ // Remove `account` from filters, only using it in the subquery
+ filters.replace('(account = {account: String})', '');
+ query +=
+ `SELECT * FROM`
+ + ` (SELECT DISTINCT * FROM transfers_from WHERE ((from = {account: String}) OR (to = {account: String})))`
+ + ` ${filters} ORDER BY block_num DESC`;
+ } else if (endpoint == "/transfers/id") {
+ query += `SELECT * FROM transfer_events ${filters} ORDER BY action_index`;
+ } else if (endpoint == "/holders") {
+ query += `SELECT account, value AS balance FROM token_holders FINAL ${filters} ORDER BY value DESC`;
+ } else if (endpoint == "/head") {
+ query += `SELECT block_num, block_id FROM cursors FINAL`;
+ } else if (endpoint == "/tokens") {
+ // NB: Using `account_balances` seems to return the most results
+ // Have to try with fully synced chain to compare with `create_events` and others
+ query += `SELECT contract, symcode FROM account_balances GROUP BY (contract, symcode) ${filters}`;
}
query += " LIMIT {limit: int}";
diff --git a/static/@openapi-to-graphql/graphql/schema.graphql b/static/@openapi-to-graphql/graphql/schema.graphql
index 3add36c..916e645 100644
--- a/static/@openapi-to-graphql/graphql/schema.graphql
+++ b/static/@openapi-to-graphql/graphql/schema.graphql
@@ -1,17 +1,24 @@
type Query {
"""
- Balances of an account.
+ Token balances of an account.
- Equivalent to GET /{chain}/balance
+ Equivalent to GET /balance
"""
- balance(account: String!, block_num: Int, chain: Chain!, contract: String, limit: Int, page: Int, symcode: String): Balance
+ balance(account: String!, contract: String, limit: Int, page: Int, symcode: String): Balance
"""
- List of available Antelope chains and corresponding latest block for which data is available.
+ Historical token balances of an account.
- Equivalent to GET /chains
+ Equivalent to GET /balance/historical
"""
- chains(limit: Int, page: Int): Chains
+ balanceHistorical(account: String!, block_num: Int!, contract: String, limit: Int, page: Int, symcode: String): BalanceHistorical
+
+ """
+ Current head block for which data is available (can be lower than head block of the chain).
+
+ Equivalent to GET /head
+ """
+ head(limit: Int, page: Int): Head
"""
Checks database connection.
@@ -23,9 +30,9 @@ type Query {
"""
List of holders of a token.
- Equivalent to GET /{chain}/holders
+ Equivalent to GET /holders
"""
- holders(chain: Chain!, contract: String!, limit: Int, page: Int, symcode: String!): Holders
+ holders(contract: String!, limit: Int, page: Int, symcode: String!): Holders
"""
Prometheus metrics.
@@ -44,33 +51,40 @@ type Query {
"""
Total supply for a token.
- Equivalent to GET /{chain}/supply
+ Equivalent to GET /supply
"""
- supply(block_num: Int, chain: Chain!, contract: String!, issuer: String, limit: Int, page: Int, symcode: String!): Supply
+ supply(block_num: Int, contract: String!, issuer: String, limit: Int, page: Int, symcode: String!): Supply
"""
List of available tokens.
- Equivalent to GET /{chain}/tokens
+ Equivalent to GET /tokens
"""
- tokens(chain: Chain!, limit: Int, page: Int): Tokens
+ tokens(limit: Int, page: Int): Tokens
"""
- Specific transfer related to a token.
+ All transfers related to a token.
- Equivalent to GET /{chain}/transfers/{trx_id}
+ Equivalent to GET /transfers
"""
- transfer(chain: Chain!, limit: Int, page: Int, trx_id: String!): Transfer2
+ transfers(block_range: [Int], contract: String!, limit: Int, page: Int, symcode: String!): Transfers
"""
- All transfers related to a token.
+ All transfers related to an account.
- Equivalent to GET /{chain}/transfers
+ Equivalent to GET /transfers/account
"""
- transfers(block_range: [Int], chain: Chain!, contract: String, from: String, limit: Int, page: Int, symcode: String, to: String): Transfers
+ transfersAccount(account: String!, block_range: [Int], contract: String, from: String, limit: Int, page: Int, symcode: String, to: String): TransfersAccount
"""
- API version and Git short commit hash.
+ Specific transfer related to a token.
+
+ Equivalent to GET /transfers/id
+ """
+ transfersId(limit: Int, page: Int, trx_id: String!): TransfersId
+
+ """
+ Api version and Git short commit hash.
Equivalent to GET /version
"""
@@ -78,30 +92,17 @@ type Query {
}
type Balance {
- data: [BalanceChange]!
+ data: [Balance2]!
meta: ResponseMetadata!
}
-type BalanceChange {
- account: String!
- action_index: Int!
- amount: BigInt!
- balance: String!
- balance_delta: BigInt!
- block_num: Int!
+type Balance2 {
+ balance: Float!
contract: String!
- precision: Int!
+ last_updated_block: Int!
symcode: String!
- timestamp: String!
- trx_id: String!
- value: Float!
}
-"""
-The `BigInt` scalar type represents non-fractional signed whole numeric values.
-"""
-scalar BigInt
-
type ResponseMetadata {
next_page: BigInt!
previous_page: BigInt!
@@ -110,30 +111,45 @@ type ResponseMetadata {
total_results: BigInt!
}
+"""
+The `BigInt` scalar type represents non-fractional signed whole numeric values.
+"""
+scalar BigInt
+
type Statistics {
bytes_read: BigInt!
elapsed: Float!
rows_read: BigInt!
}
-enum Chain {
- EOS
- WAX
-}
-
-type Chains {
- data: [DataListItem]!
+type BalanceHistorical {
+ data: [BalanceChange]!
meta: ResponseMetadata!
}
-type DataListItem {
+type BalanceChange {
+ account: String!
+ action_index: Int!
+ amount: BigInt!
+ balance: String!
+ balance_delta: BigInt!
block_num: Int!
- chain: SupportedChains!
+ contract: String!
+ precision: Int!
+ symcode: String!
+ timestamp: String!
+ trx_id: String!
+ value: Float!
+}
+
+type Head {
+ data: [Data3ListItem]!
+ meta: ResponseMetadata!
}
-enum SupportedChains {
- EOS
- WAX
+type Data3ListItem {
+ block_id: String!
+ block_num: Int!
}
type Holders {
@@ -173,11 +189,16 @@ type Supply2 {
}
type Tokens {
- data: [Supply2]!
+ data: [ModelsScope]!
meta: ResponseMetadata!
}
-type Transfer2 {
+type ModelsScope {
+ contract: String!
+ symcode: String!
+}
+
+type Transfers {
data: [Transfer]!
meta: ResponseMetadata!
}
@@ -198,7 +219,12 @@ type Transfer {
value: Float!
}
-type Transfers {
+type TransfersAccount {
+ data: [Transfer]!
+ meta: ResponseMetadata!
+}
+
+type TransfersId {
data: [Transfer]!
meta: ResponseMetadata!
}
diff --git a/static/@typespec/openapi3/openapi.json b/static/@typespec/openapi3/openapi.json
index a931736..a8d7f12 100644
--- a/static/@typespec/openapi3/openapi.json
+++ b/static/@typespec/openapi3/openapi.json
@@ -1,13 +1,13 @@
{
"openapi": "3.0.0",
"info": {
- "title": "Antelope Token API",
+ "title": "Antelope Token Api",
"summary": "Tokens information from the Antelope blockchains, powered by Substreams",
"license": {
"name": "MIT",
"url": "https://github.com/pinax-network/antelope-token-api/blob/4f4bf36341b794c0ccf5b7a14fdf810be06462d2/LICENSE"
},
- "version": "5.0.0"
+ "version": "6.0.0"
},
"tags": [
{
@@ -21,15 +21,146 @@
}
],
"paths": {
- "/chains": {
+ "/balance": {
"get": {
"tags": [
"Usage"
],
- "operationId": "Usage_chains",
- "summary": "Chains and latest block available",
- "description": "List of available Antelope chains and corresponding latest block for which data is available.",
+ "operationId": "Usage_balance",
+ "summary": "Token balances",
+ "description": "Token balances of an account.",
+ "parameters": [
+ {
+ "name": "account",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "contract",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "symcode",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "format": "uint64",
+ "default": 10
+ }
+ },
+ {
+ "name": "page",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "format": "uint64",
+ "default": 1
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Array of balances.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "data",
+ "meta"
+ ],
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Balance"
+ }
+ },
+ "meta": {
+ "$ref": "#/components/schemas/ResponseMetadata"
+ }
+ }
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An unexpected error response.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ApiError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "ApiKeyAuth": []
+ }
+ ]
+ }
+ },
+ "/balance/historical": {
+ "get": {
+ "tags": [
+ "Usage"
+ ],
+ "operationId": "Usage_balanceHistorical",
+ "summary": "Historical token balances",
+ "description": "Historical token balances of an account.",
"parameters": [
+ {
+ "name": "account",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "block_num",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "integer",
+ "format": "uint64"
+ }
+ },
+ {
+ "name": "contract",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "symcode",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
{
"name": "limit",
"in": "query",
@@ -53,7 +184,81 @@
],
"responses": {
"200": {
- "description": "Array of block information.",
+ "description": "Array of balances.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "data",
+ "meta"
+ ],
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/BalanceChange"
+ }
+ },
+ "meta": {
+ "$ref": "#/components/schemas/ResponseMetadata"
+ }
+ }
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An unexpected error response.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ApiError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "ApiKeyAuth": []
+ }
+ ]
+ }
+ },
+ "/head": {
+ "get": {
+ "tags": [
+ "Usage"
+ ],
+ "operationId": "Usage_head",
+ "summary": "Head block information",
+ "description": "Current head block for which data is available (can be lower than head block of the chain).",
+ "parameters": [
+ {
+ "name": "limit",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "format": "uint64",
+ "default": 10
+ }
+ },
+ {
+ "name": "page",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "format": "uint64",
+ "default": 1
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Head block information.",
"content": {
"application/json": {
"schema": {
@@ -68,17 +273,17 @@
"items": {
"type": "object",
"properties": {
- "chain": {
- "$ref": "#/components/schemas/SupportedChains"
- },
"block_num": {
"type": "integer",
"format": "uint64"
+ },
+ "block_id": {
+ "type": "string"
}
},
"required": [
- "chain",
- "block_num"
+ "block_num",
+ "block_id"
]
}
},
@@ -95,7 +300,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/APIError"
+ "$ref": "#/components/schemas/ApiError"
}
}
}
@@ -114,7 +319,7 @@
"parameters": [],
"responses": {
"200": {
- "description": "OK or APIError.",
+ "description": "OK or ApiError.",
"content": {
"application/json": {
"schema": {
@@ -128,7 +333,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/APIError"
+ "$ref": "#/components/schemas/ApiError"
}
}
}
@@ -136,22 +341,74 @@
}
}
},
- "/metrics": {
+ "/holders": {
"get": {
"tags": [
- "Monitoring"
+ "Usage"
+ ],
+ "operationId": "Usage_holders",
+ "summary": "Token holders",
+ "description": "List of holders of a token.",
+ "parameters": [
+ {
+ "name": "contract",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "symcode",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "format": "uint64",
+ "default": 10
+ }
+ },
+ {
+ "name": "page",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "format": "uint64",
+ "default": 1
+ }
+ }
],
- "operationId": "Monitoring_metrics",
- "summary": "Prometheus metrics",
- "description": "Prometheus metrics.",
- "parameters": [],
"responses": {
"200": {
- "description": "Metrics as text.",
+ "description": "Array of accounts.",
"content": {
"application/json": {
"schema": {
- "type": "string"
+ "type": "object",
+ "required": [
+ "data",
+ "meta"
+ ],
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Holder"
+ }
+ },
+ "meta": {
+ "$ref": "#/components/schemas/ResponseMetadata"
+ }
+ }
}
}
}
@@ -161,31 +418,35 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/APIError"
+ "$ref": "#/components/schemas/ApiError"
}
}
}
}
- }
+ },
+ "security": [
+ {
+ "ApiKeyAuth": []
+ }
+ ]
}
},
- "/openapi": {
+ "/metrics": {
"get": {
"tags": [
- "Docs"
+ "Monitoring"
],
- "operationId": "Docs_openapi",
- "summary": "OpenAPI JSON spec",
- "description": "Reflection endpoint to return OpenAPI JSON spec. Also used by Swagger to generate the frontpage.",
+ "operationId": "Monitoring_metrics",
+ "summary": "Prometheus metrics",
+ "description": "Prometheus metrics.",
"parameters": [],
"responses": {
"200": {
- "description": "The OpenAPI JSON spec",
+ "description": "Metrics as text.",
"content": {
"application/json": {
"schema": {
- "type": "object",
- "additionalProperties": {}
+ "type": "string"
}
}
}
@@ -195,7 +456,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/APIError"
+ "$ref": "#/components/schemas/ApiError"
}
}
}
@@ -203,22 +464,23 @@
}
}
},
- "/version": {
+ "/openapi": {
"get": {
"tags": [
"Docs"
],
- "operationId": "Docs_version",
- "summary": "API version",
- "description": "API version and Git short commit hash.",
+ "operationId": "Docs_openapi",
+ "summary": "OpenAPI JSON spec",
+ "description": "Reflection endpoint to return OpenAPI JSON spec. Also used by Swagger to generate the frontpage.",
"parameters": [],
"responses": {
"200": {
- "description": "The API version and commit hash.",
+ "description": "The OpenAPI JSON spec",
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/Version"
+ "type": "object",
+ "additionalProperties": {}
}
}
}
@@ -228,7 +490,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/APIError"
+ "$ref": "#/components/schemas/ApiError"
}
}
}
@@ -236,23 +498,15 @@
}
}
},
- "/{chain}/balance": {
+ "/supply": {
"get": {
"tags": [
"Usage"
],
- "operationId": "Usage_balance",
- "summary": "Token balance",
- "description": "Balances of an account.",
+ "operationId": "Usage_supply",
+ "summary": "Token supply",
+ "description": "Total supply for a token.",
"parameters": [
- {
- "name": "chain",
- "in": "path",
- "required": true,
- "schema": {
- "$ref": "#/components/schemas/SupportedChains"
- }
- },
{
"name": "block_num",
"in": "query",
@@ -263,7 +517,7 @@
}
},
{
- "name": "contract",
+ "name": "issuer",
"in": "query",
"required": false,
"schema": {
@@ -271,15 +525,15 @@
}
},
{
- "name": "symcode",
+ "name": "contract",
"in": "query",
- "required": false,
+ "required": true,
"schema": {
"type": "string"
}
},
{
- "name": "account",
+ "name": "symcode",
"in": "query",
"required": true,
"schema": {
@@ -309,7 +563,7 @@
],
"responses": {
"200": {
- "description": "Array of balances.",
+ "description": "Array of supplies.",
"content": {
"application/json": {
"schema": {
@@ -322,7 +576,7 @@
"data": {
"type": "array",
"items": {
- "$ref": "#/components/schemas/BalanceChange"
+ "$ref": "#/components/schemas/Supply"
}
},
"meta": {
@@ -338,47 +592,28 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/APIError"
+ "$ref": "#/components/schemas/ApiError"
}
}
}
}
- }
+ },
+ "security": [
+ {
+ "ApiKeyAuth": []
+ }
+ ]
}
},
- "/{chain}/holders": {
+ "/tokens": {
"get": {
"tags": [
"Usage"
],
- "operationId": "Usage_holders",
- "summary": "Token holders",
- "description": "List of holders of a token.",
+ "operationId": "Usage_tokens",
+ "summary": "Tokens",
+ "description": "List of available tokens.",
"parameters": [
- {
- "name": "chain",
- "in": "path",
- "required": true,
- "schema": {
- "$ref": "#/components/schemas/SupportedChains"
- }
- },
- {
- "name": "contract",
- "in": "query",
- "required": true,
- "schema": {
- "type": "string"
- }
- },
- {
- "name": "symcode",
- "in": "query",
- "required": true,
- "schema": {
- "type": "string"
- }
- },
{
"name": "limit",
"in": "query",
@@ -402,7 +637,7 @@
],
"responses": {
"200": {
- "description": "Array of accounts.",
+ "description": "Array of token identifier.",
"content": {
"application/json": {
"schema": {
@@ -415,7 +650,7 @@
"data": {
"type": "array",
"items": {
- "$ref": "#/components/schemas/Holder"
+ "$ref": "#/components/schemas/Models.Scope"
}
},
"meta": {
@@ -431,47 +666,41 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/APIError"
+ "$ref": "#/components/schemas/ApiError"
}
}
}
}
- }
+ },
+ "security": [
+ {
+ "ApiKeyAuth": []
+ }
+ ]
}
},
- "/{chain}/supply": {
+ "/transfers": {
"get": {
"tags": [
"Usage"
],
- "operationId": "Usage_supply",
- "summary": "Token supply",
- "description": "Total supply for a token.",
+ "operationId": "Usage_transfers",
+ "summary": "Token transfers",
+ "description": "All transfers related to a token.",
"parameters": [
{
- "name": "chain",
- "in": "path",
- "required": true,
- "schema": {
- "$ref": "#/components/schemas/SupportedChains"
- }
- },
- {
- "name": "block_num",
- "in": "query",
- "required": false,
- "schema": {
- "type": "integer",
- "format": "uint64"
- }
- },
- {
- "name": "issuer",
+ "name": "block_range",
"in": "query",
"required": false,
"schema": {
- "type": "string"
- }
+ "type": "array",
+ "items": {
+ "type": "integer",
+ "format": "uint64"
+ }
+ },
+ "style": "form",
+ "explode": false
},
{
"name": "contract",
@@ -512,7 +741,7 @@
],
"responses": {
"200": {
- "description": "Array of supplies.",
+ "description": "Array of transfers.",
"content": {
"application/json": {
"schema": {
@@ -525,7 +754,7 @@
"data": {
"type": "array",
"items": {
- "$ref": "#/components/schemas/Supply"
+ "$ref": "#/components/schemas/Transfer"
}
},
"meta": {
@@ -541,106 +770,34 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/APIError"
+ "$ref": "#/components/schemas/ApiError"
}
}
}
}
- }
- }
- },
- "/{chain}/tokens": {
- "get": {
- "tags": [
- "Usage"
- ],
- "operationId": "Usage_tokens",
- "summary": "Tokens",
- "description": "List of available tokens.",
- "parameters": [
- {
- "name": "chain",
- "in": "path",
- "required": true,
- "schema": {
- "$ref": "#/components/schemas/SupportedChains"
- }
- },
- {
- "name": "limit",
- "in": "query",
- "required": false,
- "schema": {
- "type": "integer",
- "format": "uint64",
- "default": 10
- }
- },
+ },
+ "security": [
{
- "name": "page",
- "in": "query",
- "required": false,
- "schema": {
- "type": "integer",
- "format": "uint64",
- "default": 1
- }
- }
- ],
- "responses": {
- "200": {
- "description": "Array of supplies.",
- "content": {
- "application/json": {
- "schema": {
- "type": "object",
- "required": [
- "data",
- "meta"
- ],
- "properties": {
- "data": {
- "type": "array",
- "items": {
- "$ref": "#/components/schemas/Supply"
- }
- },
- "meta": {
- "$ref": "#/components/schemas/ResponseMetadata"
- }
- }
- }
- }
- }
- },
- "default": {
- "description": "An unexpected error response.",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/APIError"
- }
- }
- }
+ "ApiKeyAuth": []
}
- }
+ ]
}
},
- "/{chain}/transfers": {
+ "/transfers/account": {
"get": {
"tags": [
"Usage"
],
- "operationId": "Usage_transfers",
- "summary": "Token transfers",
- "description": "All transfers related to a token.",
+ "operationId": "Usage_transfersAccount",
+ "summary": "Token transfers from and to an account",
+ "description": "All transfers related to an account.",
"parameters": [
{
- "name": "chain",
- "in": "path",
+ "name": "account",
+ "in": "query",
"required": true,
"schema": {
- "$ref": "#/components/schemas/SupportedChains"
+ "type": "string"
}
},
{
@@ -741,34 +898,31 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/APIError"
+ "$ref": "#/components/schemas/ApiError"
}
}
}
}
- }
+ },
+ "security": [
+ {
+ "ApiKeyAuth": []
+ }
+ ]
}
},
- "/{chain}/transfers/{trx_id}": {
+ "/transfers/id": {
"get": {
"tags": [
"Usage"
],
- "operationId": "Usage_transfer",
+ "operationId": "Usage_transferId",
"summary": "Token transfer",
"description": "Specific transfer related to a token.",
"parameters": [
- {
- "name": "chain",
- "in": "path",
- "required": true,
- "schema": {
- "$ref": "#/components/schemas/SupportedChains"
- }
- },
{
"name": "trx_id",
- "in": "path",
+ "in": "query",
"required": true,
"schema": {
"type": "string"
@@ -826,7 +980,45 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/APIError"
+ "$ref": "#/components/schemas/ApiError"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "ApiKeyAuth": []
+ }
+ ]
+ }
+ },
+ "/version": {
+ "get": {
+ "tags": [
+ "Docs"
+ ],
+ "operationId": "Docs_version",
+ "summary": "Api version",
+ "description": "Api version and Git short commit hash.",
+ "parameters": [],
+ "responses": {
+ "200": {
+ "description": "The Api version and commit hash.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Version"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An unexpected error response.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ApiError"
}
}
}
@@ -837,7 +1029,7 @@
},
"components": {
"schemas": {
- "APIError": {
+ "ApiError": {
"type": "object",
"required": [
"status",
@@ -877,6 +1069,31 @@
}
}
},
+ "Balance": {
+ "type": "object",
+ "required": [
+ "last_updated_block",
+ "contract",
+ "symcode",
+ "balance"
+ ],
+ "properties": {
+ "last_updated_block": {
+ "type": "integer",
+ "format": "uint64"
+ },
+ "contract": {
+ "type": "string"
+ },
+ "symcode": {
+ "type": "string"
+ },
+ "balance": {
+ "type": "number",
+ "format": "double"
+ }
+ }
+ },
"BalanceChange": {
"type": "object",
"required": [
@@ -954,6 +1171,21 @@
}
}
},
+ "Models.Scope": {
+ "type": "object",
+ "required": [
+ "contract",
+ "symcode"
+ ],
+ "properties": {
+ "contract": {
+ "type": "string"
+ },
+ "symcode": {
+ "type": "string"
+ }
+ }
+ },
"Pagination": {
"type": "object",
"required": [
@@ -1104,13 +1336,6 @@
}
}
},
- "SupportedChains": {
- "type": "string",
- "enum": [
- "EOS",
- "WAX"
- ]
- },
"Transfer": {
"type": "object",
"required": [
@@ -1192,6 +1417,13 @@
}
}
}
+ },
+ "securitySchemes": {
+ "ApiKeyAuth": {
+ "type": "apiKey",
+ "in": "header",
+ "name": "X-Api-Key"
+ }
}
}
}
diff --git a/swagger/index.html b/swagger/index.html
index b17f0bf..9267022 100644
--- a/swagger/index.html
+++ b/swagger/index.html
@@ -16,10 +16,11 @@