app.ts
Overview
`app.ts` is the main entry point for the **ShapeShift Arbitrum Nova API** server application. It sets up an Express-based HTTP server with integrated WebSocket support to handle blockchain data streaming and API requests. This file orchestrates the initialization and configuration of:
HTTP REST API endpoints including health checks, metrics, and Swagger-based API documentation.
WebSocket client connection to an external blockchain indexer for real-time event streaming.
Blockchain data processing via block and transaction handlers.
Metrics collection through Prometheus.
Middleware for request handling and error management.
Routing via a centralized route registration utility.
The application is specifically tailored for the Arbitrum Nova coinstack and leverages ShapeShift’s common APIs and utilities for blockchain data processing.
Detailed Components
Imports and Constants
Express: HTTP server framework.
path.join: For static file path resolution.
ws.Server: WebSocket server for client connections.
swagger-ui-express: Serves Swagger UI for API documentation.
@shapeshiftoss/common-api: Provides blockchain-specific middleware, handlers, and Prometheus integration.
@shapeshiftoss/blockbook: Blockchain indexer client library with types and utilities.
@shapeshiftoss/logger: Structured logging utility.
Local
controller: Business logic for gas oracle and blockchain service.Local
routes: API route registration.Environment variables:
PORT(default 3000)INDEXER_WS_URL(required WebSocket URL for blockchain indexer)
Logger
export const logger = new Logger({
namespace: ['unchained', 'coinstacks', 'arbitrum-nova', 'api'],
level: process.env.LOG_LEVEL,
})
Provides contextual logging with namespace for easier troubleshooting.
Log level configurable via environment variable.
Prometheus Metrics
const prometheus = new Prometheus({ coinstack: 'arbitrum-nova' })
Collects and exposes metrics specific to the Arbitrum Nova coinstack.
Integrated into middleware for request metrics.
Express App Setup
const app = express()
app.use(...middleware.common(prometheus))
Uses common middleware from
@shapeshiftoss/common-apiwhich likely includes:Request logging
Metrics collection
JSON body parsing
CORS handling (typical in common middleware)
HTTP Routes
Health Check Endpoint
app.get('/health', async (_, res) =>
res.json({ status: 'up', asset: 'arbitrum-nova', connections: wsServer.clients.size })
)
Returns service status including number of active WebSocket connections.
Metrics Endpoint
app.get('/metrics', async (_, res) => {
res.setHeader('Content-Type', prometheus.register.contentType)
res.send(await prometheus.register.metrics())
})
Exposes Prometheus metrics.
Static and Swagger UI
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 API documentation at `/docs` with custom branding and favicon.
Route Registration
RegisterRoutes(app)
Dynamically adds all API routes from the `routes` module.
Root Redirect
app.get('/', async (_, res) => {
res.redirect('/docs')
})
Redirects root path to API docs.
Error Handling Middleware
app.use(middleware.errorHandler, middleware.notFoundHandler)
Handles 404 and other errors gracefully.
Blockchain Handlers
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 }
}
Processes a new block event by:
Fetching block transactions and internal transactions in parallel.
Transforming transactions with internal trace data.
Aggregating unique addresses involved.
Returns an array of objects containing addresses and processed transactions.
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 }
}
Processes individual transactions with internal trace data.
Extracts all related addresses.
Returns the enhanced transaction and address list.
Registry and WebSocket Client
const registry = new Registry({ addressFormatter: evm.formatAddress, blockHandler, transactionHandler })
Central registry that formats addresses and delegates to handlers.
Used by WebSocket client to process blockchain events.
const blockbook = new WebsocketClient(INDEXER_WS_URL, {
blockHandler: [registry.onBlock.bind(registry), gasOracle.onBlock.bind(gasOracle)],
transactionHandler: registry.onTransaction.bind(registry),
})
Connects to external blockchain indexer WebSocket.
Registers handlers for new blocks and transactions.
Integrates
gasOracleupdates on new blocks.
Server and WebSocket Server Initialization
const server = app.listen(PORT, () => logger.info('Server started'))
const wsServer = new Server({ server })
Starts HTTP server.
Creates WebSocket server on top of HTTP server.
wsServer.on('connection', (connection) => {
ConnectionHandler.start(connection, registry, blockbook, prometheus, logger)
})
Handles new WebSocket client connections.
Starts connection lifecycle management with blockchain data, metrics, and logging.
Usage Examples
Starting the Server
Set environment variables:
export INDEXER_WS_URL=wss://indexer.arbitrum-nova.example/ws
export PORT=3000
export LOG_LEVEL=info
Run:
node dist/app.js
Accessing API
Health check:
curl http://localhost:3000/health
Metrics:
curl http://localhost:3000/metrics
API Docs:
Open browser at `http://localhost:3000/docs`
WebSocket Client Connection
Clients can connect to the WebSocket server at the same port and receive real-time blockchain updates managed by `ConnectionHandler`.
Important Implementation Details
Parallel Data Fetching: In block handler, internal transactions and block transactions are fetched concurrently for efficiency.
Address Aggregation: Uses
Setto ensure unique addresses from both external and internal transactions.Middleware Usage: Common middleware includes Prometheus integration for request metrics and error handling.
Registry Pattern:
Registryacts as central event router for blockchain data events.Dependency Injection: Handlers and formatter provided to
Registryto keep it decoupled and testable.WebSocket Client and Server: Bridges external blockchain data with internal WebSocket clients.
Swagger UI Customization: Branded API docs enhance developer experience.
Interaction with Other Parts of the System
@shapeshiftoss/common-api: Provides middleware, handlers, and Prometheus utilities.@shapeshiftoss/blockbook: Supplies WebSocket client and transaction/block types.controllermodule: Contains core blockchain processing logic (service) and gas oracle updates.routesmodule: Defines REST API endpoints registered on the Express app.External Blockchain Indexer: Connected via WebSocket to receive real-time data.
Clients: Connect via HTTP for REST API and via WebSocket for streaming blockchain data.
Visual Diagram
classDiagram
class App {
+express app
+wsServer: WebSocket.Server
+logger: Logger
+prometheus: Prometheus
+registry: Registry
+blockbook: WebsocketClient
+blockHandler(block: NewBlock): Promise
+transactionHandler(tx: BlockbookTx): Promise
+start()
}
class Registry {
+addressFormatter
+blockHandler
+transactionHandler
+onBlock()
+onTransaction()
}
class WebsocketClient {
+blockHandler: Function[]
+transactionHandler: Function
+connect()
}
class ConnectionHandler {
+start(connection, registry, blockbook, prometheus, logger)
}
App --> Registry : uses
App --> WebsocketClient : creates
App --> ConnectionHandler : calls on ws connection
WebsocketClient --> Registry : calls onBlock, onTransaction
Registry --> App.blockHandler : delegates block handling
Registry --> App.transactionHandler : delegates transaction handling
Summary
`app.ts` effectively combines Express REST API, Prometheus metrics, Swagger docs, and WebSocket servers to deliver a robust blockchain event streaming and querying API service for Arbitrum Nova. It leverages modular design principles, concurrency for efficient data fetching, and integrates several ShapeShift OSS libraries to handle blockchain-specific logic, making it the backbone of the Arbitrum Nova indexing API.