app.ts
Overview
`app.ts` is the main entry point for the ShapeShift Polygon API server application. It sets up an Express.js HTTP server combined with a WebSocket server to provide real-time blockchain data indexing and transaction processing for the Polygon network. This file orchestrates middleware, API routes, WebSocket event handling, and integrates with blockchain data sources to deliver a scalable, observable, and documented API service.
Key functionalities include:
HTTP REST API with health checks, Prometheus metrics, and Swagger-based API documentation.
WebSocket client integration with an external Polygon blockchain indexer (via
blockbook), enabling live block and transaction streaming.Custom block and transaction handlers that process and enrich blockchain data before distribution.
Metrics instrumentation and logging for observability and debugging.
Dynamic routing and error handling middleware.
Overall, `app.ts` serves as the coordinator that wires together core components, middleware, and external services into a cohesive API server for Polygon blockchain data.
Detailed Explanations
Constants and Environment Variables
Name | Purpose |
|---|---|
`PORT` | Port number for the Express HTTP server (default 3000) |
`INDEXER_WS_URL` | WebSocket URL for the Polygon blockchain indexer |
`INDEXER_API_KEY` | API key for authenticating with the blockchain indexer |
`INDEXER_WS_URL` is mandatory and the app throws an error if not set.
Logger Instance
export const logger = new Logger({
namespace: ['unchained', 'coinstacks', 'polygon', 'api'],
level: process.env.LOG_LEVEL,
})
A namespaced logger configured with a dynamic log level.
Provides structured logging across the app.
Express Application Setup
const app = express()
app.use(...middleware.common(prometheus))
Sets up an Express app instance.
Applies common middleware including Prometheus instrumentation for metrics collection.
HTTP Routes
Health Check Route
app.get('/health', async (_, res) => res.json({ status: 'up', asset: 'polygon', connections: wsServer.clients.size }))
Returns a JSON response indicating API health and current WebSocket client connections.
Metrics Route
app.get('/metrics', async (_, res) => {
res.setHeader('Content-Type', prometheus.register.contentType)
res.send(await prometheus.register.metrics())
})
Exposes Prometheus metrics endpoint for monitoring.
Swagger API Documentation
const options: swaggerUi.SwaggerUiOptions = {
customCss: '.swagger-ui .topbar { display: none }',
customSiteTitle: 'ShapeShift Polygon API Docs',
customfavIcon: '/public/favi-blue.png',
swaggerUrl: '/swagger.json',
}
app.use('/public', express.static(join(__dirname, '../../../../../../common/api/public')))
app.use('/swagger.json', express.static(join(__dirname, './swagger.json')))
app.use('/docs', swaggerUi.serve, swaggerUi.setup(undefined, options))
Serves static assets and Swagger UI for API documentation at
/docs.Customizes the Swagger UI appearance and title.
Register API Routes
RegisterRoutes(app)
Registers all REST API routes defined in
./routes.
Redirect Root to Docs
app.get('/', async (_, res) => {
res.redirect('/docs')
})
Redirects the root URL
/to the API documentation page/docs.
Error Handling Middleware
app.use(middleware.errorHandler, middleware.notFoundHandler)
Applies global error handling and 404 not found middleware.
Blockchain Data Handlers
These handlers process blockchain data received from the Polygon indexer.
Block Handler
const blockHandler: BlockHandler<NewBlock, Array<{ addresses: Array<string>; tx: evm.Tx }>> = async (block) => {
const [blockbookTxs, internalTxs] = await Promise.all([
service.handleBlock(block.hash),
service.fetchInternalTxsByBlockDebug(block.hash),
])
const txs = blockbookTxs.map((t) => {
const tx = service.handleTransaction(t)
tx.internalTxs = internalTxs[t.txid]
const internalAddresses = (tx.internalTxs ?? []).reduce<Array<string>>((prev, tx) => [...prev, tx.to, tx.from], [])
const addresses = [...new Set([...getAddresses(t), ...internalAddresses])]
return { addresses, tx }
})
return { txs }
}
Parameters:
block: The new block data (NewBlock) from the indexer.
Process:
Fetches transactions in the block and their internal transactions concurrently.
Processes each transaction to enrich it with internal transactions.
Extracts all involved addresses from both external and internal txs.
Returns:
Array of objects containing involved addresses and their associated transactions.
Usage:
Used by the
Registryto handle incoming blocks and update state.
Transaction Handler
const transactionHandler: TransactionHandler<BlockbookTx, evm.Tx> = async (blockbookTx) => {
const tx = await service.handleTransactionWithInternalTrace(blockbookTx)
const internalAddresses = (tx.internalTxs ?? []).reduce<Array<string>>((prev, tx) => [...prev, tx.to, tx.from], [])
const addresses = [...new Set([...getAddresses(blockbookTx), ...internalAddresses])]
return { addresses, tx }
}
Parameters:
blockbookTx: A raw transaction from the blockbook client.
Process:
Processes transaction including internal trace data.
Extracts all related addresses.
Returns:
Object with addresses and processed transaction.
Usage:
Used by the
Registryto handle individual transactions streaming from the indexer.
Registry Instance
const registry = new Registry({ addressFormatter: evm.formatAddress, blockHandler, transactionHandler })
Manages blockchain state and dispatches block/transaction events.
Uses Ethereum Virtual Machine (EVM) address formatter.
Hooks in the defined block and transaction handlers.
Blockbook WebSocket Client
const blockbook = new WebsocketClient(INDEXER_WS_URL, {
apiKey: INDEXER_API_KEY,
blockHandler: [registry.onBlock.bind(registry), gasOracle.onBlock.bind(gasOracle)],
transactionHandler: registry.onTransaction.bind(registry),
})
Connects to Polygon blockchain indexer via WebSocket.
Authenticates using API key.
Subscribes to block events with multiple handlers:
registry.onBlockto update registry state.gasOracle.onBlockto update gas price oracles.
Subscribes to transaction events via
registry.onTransaction.
Server and WebSocket Server Initialization
const server = app.listen(PORT, () => logger.info('Server started'))
const wsServer = new Server({ server })
wsServer.on('connection', (connection) => {
ConnectionHandler.start(connection, registry, blockbook, prometheus, logger)
})
Starts HTTP server on specified port.
Initializes a WebSocket server attached to the HTTP server.
On new WebSocket client connection:
Delegates connection handling to
ConnectionHandler.start, passing registry, blockbook client, Prometheus metrics, and logger.Enables real-time interaction with clients subscribed to blockchain data updates.
Important Implementation Details and Algorithms
Concurrency in Block Handler: Utilizes
Promise.allto fetch block transactions and internal transactions simultaneously for performance.Address Deduplication: Uses
Setto deduplicate addresses extracted from both external and internal transactions before returning.Middleware Composition: Applies common middleware from
@shapeshiftoss/common-api, including Prometheus metrics, error handling, and 404 handling, ensuring consistent application behavior.Modular Handlers: Block and transaction handlers are injected into the
Registry, promoting separation of concerns and easy extensibility.WebSocket Multiplexing: The block handler array allows multiple subscribers (e.g., registry and gas oracle) to respond to block events independently.
Swagger Integration: Static files for Swagger UI and JSON specs are served directly, enabling easy API documentation access.
Interaction with Other Parts of the System
@shapeshiftoss/common-api: Provides middleware, registry, block and transaction handlers, Prometheus monitoring, and EVM utilities.
@shapeshiftoss/blockbook: Supplies the WebSocket client to connect to the Polygon blockchain indexer and utility functions like
getAddresses.Controller (
./controller): Implementsservice(business logic for handling blocks and transactions) andgasOracle(gas price tracking).Routes (
./routes): Defines the REST API endpoints registered on the Express app.Logger (
@shapeshiftoss/logger): Centralized logging facility.WebSocket ConnectionHandler: Manages client WebSocket connections, linking them to the blockchain data streams and metrics.
Usage Example
The server is started simply by running the compiled `app.ts` (usually via `ts-node` or after build):
INDEXER_WS_URL=wss://polygon-indexer.example.com/ws \
INDEXER_API_KEY=yourapikey \
PORT=3000 \
npm start
Clients can:
Query REST endpoints such as
/health,/metrics, and API routes under/docs.Connect to the WebSocket server on the same port to receive live updates.
Visual Diagram
classDiagram
class App {
-app: Express.Application
-wsServer: WebSocket.Server
-blockbook: WebsocketClient
-registry: Registry
-prometheus: Prometheus
-logger: Logger
+start()
}
class Registry {
+onBlock()
+onTransaction()
}
class WebsocketClient {
+blockHandler
+transactionHandler
+connect()
}
class ConnectionHandler {
+start(connection, registry, blockbook, prometheus, logger)
}
class Express {
+use()
+get()
+listen()
}
App --> Express : uses
App --> WebsocketClient : creates
App --> Registry : creates
App --> ConnectionHandler : uses
WebsocketClient --> Registry : calls onBlock/onTransaction
ConnectionHandler --> Registry : interacts with
ConnectionHandler --> WebsocketClient : interacts with
Summary
The `app.ts` file is the backbone of the ShapeShift Polygon API, integrating HTTP and WebSocket servers, blockchain data streaming, and monitoring. It leverages modular components from the `@shapeshiftoss` ecosystem and external services to provide a robust, documented API for Polygon blockchain data. The architecture promotes separation of concerns, observability, and scalability.